# PR #23605 runtime evidence: Tailscale Serve keeps retrying after a failed start

- Machine: fs-29 (Linux 6.1), real Tailscale 1.104.1, logged in. Run on 2026-10-22, 12:36–12:43 local.
- before = `/home/ubuntu/wt/pr-23605-before` @ `8d3b061121fe65669d5d43d011b48cd3027a4108` (main)
- after  = `/home/ubuntu/wt/pr-23605` @ `e3d87aa2eabd0ca51cfed812693caed1e53e9662` (PR head)
- Command (from each checkout, `T3CODE_*` unset, fresh `--base-dir` per row/side):
  `node apps/server/src/bin.ts serve --tailscale-serve --tailscale-serve-port 18443 --port 18638 --host 127.0.0.1 --base-dir /tmp/ev-23605/<row>-<side>/base --no-browser`
- `/tmp/ev-23605/bin/tailscale` was first on PATH for the server process only. It logs each call to `<row>-<side>/calls.log` and dispatches on MODE (real / nosock / notloggedin, the last being a **FAKE-CLI** that prints `Logged out. You are not logged in` until the flag file exists).
- The driver (`bin/driver.sh <row> <side>`) records every step, serve status and operator change in `<row>-<side>/driver.log`. Server stdout/stderr is in `server.log`, with each line prefixed by the wall-clock time it was received.
- Serve status: baseline `{}`, and `{}` at the end (`serve-after.json`). The operator was empty before and is empty after.
- The server takes about 6 s from spawn to its first `serve` call because of boot time. Attempt times below are relative to that first call.
- The real CLI with a missing socket takes about 5 s to fail. Its stderr reads `...Failed to connect to local Tailscale daemon...; not running? Error: dial unix ... no such file or directory`, which the package classifies as `unknown`.
- In the after build, a repeated identical failure logs at DEBUG. At the default log level each after row therefore shows exactly one WARN, even though there were several attempts.

## R5: first attempt succeeds (MODE=real, operator granted before start)
| | before | after |
|---|---|---|
| calls | 12:36:40.120 serve --bg; 12:36:42.277 serve off | 12:36:23.619 serve --bg; 12:36:25.858 serve off |
| log | 40.150 `Tailscale Serve configured`; 42.294 `Tailscale Serve disabled` | 23.664 `configured`; 25.878 `disabled` |
| serve status | start `{}` → configured 18443→http://127.0.0.1:18638 → after SIGTERM `{}` | same |
| exit after SIGTERM | ~0.7 s | ~1.0 s |

Verdict: **yes**. There is no regression on the happy path. The after build makes one call, then turns Serve off on shutdown.

## R1c: daemon not running → running (MODE=nosock, real CLI error)
- **before**: one attempt at 12:36:50.080, then `WARN Failed to configure Tailscale Serve` (12:36:55.185). The operator was granted and the flag set at 12:37:24.7. In the following 48 s there were **no further calls** and serve status stayed `{}`. After SIGTERM: 0 further calls, `{}`.
- **after**: attempts at 12:38:21.502, 27.614, 34.725, 43.837, 56.948. Each failure takes ~5 s, followed by backoff gaps of ≈1, 2, 4 and 8 s. The single WARN at 12:38:26.600 reads: "Failed to configure Tailscale Serve. Run `tailscale serve --bg --https=18443 http://127.0.0.1:18638` by hand to see why. Retrying in the background." The diagnostic was `unknown`. The flag was set at 12:38:54.718, and the next attempt at 12:38:56.948 succeeded: `INFO Tailscale Serve configured` (56.967), with serve showing 18443. SIGTERM at 12:38:58.042 produced `serve --https=18443 off` at 58.062 and `INFO Tailscale Serve disabled` at 58.083. The server exited ~0.2 s after SIGTERM, and serve status was `{}`.

Verdict: **yes**.

## R1b: not logged in → logged in (**FAKE-CLI**, MODE=notloggedin)
- **before**: one attempt at 12:39:05.173, then WARN `Failed to configure Tailscale Serve` with no remedy. The operator was granted and the flag set at 12:39:39.56. In the following 48 s there were no further calls and serve status stayed `{}`. After SIGTERM: 0 calls, `{}`.
- **after**: WARN at 12:40:34.218: "Failed to configure Tailscale Serve. **Run `tailscale up` to log in.** Retrying in the background." Attempts came at 34.179, 35.234, 37.249, 41.266, 49.282, 12:41:05.300, then 12:41:35.315. The gaps were ≈1, 2, 4, 8, 16 s, then capped at **30 s**. The operator was granted and the flag set at 12:41:09.05. The next scheduled attempt (35.315) succeeded: `configured` at 12:41:36.120, and serve showed 18443. SIGTERM at 37.247 produced `serve off` at 37.263 and `disabled` at 37.346, then the exit and `{}`.

Verdict: **yes**. The remedy says `tailscale up`, and the backoff is capped at 30 s.

## R2: never succeeds → SIGTERM (MODE=real, no operator; stands in for the desktop toggle-off)
- **before**: one attempt at 12:41:44.935, then WARN `Failed to configure Tailscale Serve`. SIGTERM at 12:42:19.224, exit at 19.431. There were 0 calls after SIGTERM and no `serve off`. Serve status was `{}` throughout.
- **after**: attempts at 12:42:25.818, 26.864, 28.895, 32.925, 40.963 and 56.995, giving gaps of ≈1, 2, 4, 8, 16 s. The single WARN at 25.849 reads: "**On Linux, run `sudo tailscale set --operator=$USER`** to let this user configure Serve. Retrying in the background." SIGTERM came at 12:42:59.121, during the backoff sleep (~2.1 s after the last attempt). The server **exited by 12:42:59.328 (~0.2 s)**. There were **0 calls after SIGTERM and no `serve off`**. Serve status was `{}` at 40 s and after SIGTERM.

Verdict: **yes**.

## R1a: permission denied → operator granted (MODE=real, after only)
- **after**: attempts at 12:43:06.787, 07.837, 09.867, 13.898 and 21.929, giving gaps of ≈1, 2, 4, 8 s. The single WARN at 06.823 carries the operator remedy. Serve status at 20 s was `{}`. The operator was granted at 12:43:20.458. The next attempt (21.929) succeeded: `configured` at 21.946, with serve showing 18443. SIGTERM at 22.987 produced `serve off` at 23.003 and `disabled` at 23.020. The server exited ~0.2 s later, serve status was `{}`, and the operator was restored to empty.

Verdict: **yes**.

## Summary
| Row | before | after | behaves as PR claims |
|---|---|---|---|
| R5 first try succeeds | configured/disabled | configured/disabled | yes |
| R1c not running → running | 1 failure, never recovers | retries 1/2/4/8 s, configured, disabled on SIGTERM | yes |
| R1b not logged in (FAKE-CLI) | 1 failure, never recovers | `tailscale up` remedy, retries capped at 30 s, configured, disabled | yes |
| R2 never succeeds → SIGTERM | 1 failure, exit | retries; SIGTERM mid-backoff exits ~0.2 s, no further calls, no `serve off`, `{}` | yes |
| R1a permission denied → operator | n/a | operator remedy, retries, configured after grant, disabled | yes |

## Final state (verified 12:46:56)
- `tailscale serve status --json` = `{}` (equal to serve-before.json)
- `sudo tailscale debug prefs | grep -i operator`: no match (OperatorUser empty/omitted)
- sha256 driver.sh: `7a6166f969e5accc5946e70f51255be0ecd1a5efeada8c496176f55be4a1d0f0`
- sha256 wrapper bin/tailscale: `77bdde626ec7652ef49fefa464ba44917f51b0ac82827a1b1a350f063025e3eb`
