Skip to content

First-class Tailscale support (status, serve/funnel, doctor, docs) - #2451

Open
sethkarten wants to merge 10 commits into
mainfrom
rsi/tailscale-support
Open

sethkarten wants to merge 10 commits into
mainfrom
rsi/tailscale-support

Conversation

@sethkarten

@sethkarten sethkarten commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Motivation

Every AI harness needs to support Tailscale: agents run in containers, on laptops, and in CI; Tailscale is the standard zero-config way to reach them (user directive). This gives prime-agent first-class Tailscale awareness instead of leaving users to hand-wire it.

What it adds

  • prime-agent tailscale - tailnet state, MagicDNS name, and served endpoints (listen/path -> target from tailscale serve status --json)
  • prime-agent tailscale serve --port <n> [--funnel] - wraps tailscale serve --bg localhost:<n> / tailscale funnel --bg localhost:<n>; requires an explicit --port (never guesses a default), validates 1-65535 BEFORE any side effect, exits 1 on every failure
  • post-serve verification: tailscale can exit 0 after only printing an interactive enable URL without configuring anything (enableFeatureInteractive); the wrapper re-reads serve status --json and refuses success when the target is absent
  • --json machine-readable status
  • doctor integration (human mode only; doctor --json stays pure JSON)
  • docs: four patterns (Tailscale SSH remote control, serve/funnel exposure, MagicDNS reach from cloud containers, adding Tailscale's MCP server)

Backend-state diagnosis

BackendState drives the diagnosis, not just Self.Online: a node whose daemon is Running but currently unreachable is reported "up on a tailnet (currently offline)", not wrongly told to run tailscale up. Top-level MagicDNSSuffix is deprecated upstream; CurrentTailnet.MagicDNSSuffix is preferred when present.

Verification

  • 16 vitest tests: probe/stopped/offline states, serve argv construction (serve|funnel --bg localhost:<n> pinned exactly), post-serve verification (pending-enable detection), parser forms (--port n, --port=n, bare serve refusal, unknown subcommand errors), status failure diagnostics (empty-stderr case)
  • CLI detection uses Node ENOENT (no which dependency; Windows-safe)
  • serve spawn: stdin inherited (funnel's first enable prompts interactively), 60s timeout
  • dual-reviewer gate passed (Macroscope + Bugbot personas, empirical probes); all 13+ findings fixed in-branch
  • npm run check green (budget-clean: test lines <= source lines)

Note

Medium Risk
New serve/funnel wrappers change network exposure for local ports, though use is explicit and verified; otherwise CLI probing and doctor output only.

Overview
Adds prime-agent tailscale as a tailnet-aware CLI: default status reports MagicDNS, node hostname, and local serve mappings (human or --json), and serve --port <n> [--funnel] wraps tailscale serve|funnel --bg localhost:<n> with pre-flight checks (CLI present, node on tailnet, port 1–65535, no default port) and post-serve verification via serve status --json so exit 0 alone cannot claim success when the port is not actually served or funnel is not enabled.

probeTailscale distinguishes missing CLI, failing status, stopped backend vs running-but-offline, and prefers CurrentTailnet.MagicDNSSuffix. Non-JSON doctor appends one-line Tailscale facts; doctor --json is unchanged.

Docs in docs/tailscale.md and a changelog entry cover Tailscale SSH, serve/funnel, container MagicDNS, and Tailscale MCP. Vitest shims cover parsing edge cases (including refusing accidental public --funnel), serve argv, and exact port matching in verification.

Reviewed by Cursor Bugbot for commit 6275e7c. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add tailscale CLI command for status, serve/funnel, and doctor detection

  • Adds a tailscale CLI command with status (human and --json output) and serve --port / --funnel subcommands, wired through the public command dispatcher in public-command.ts
  • Implements probeTailscale detection distinguishing missing CLI, failing CLI, stopped backend, online node, and offline-but-configured state, exposing MagicDNS suffix and hostname
  • Integrates a Tailscale diagnostic line into non-JSON doctor output via tailscaleDoctorFacts
  • Adds documentation in tailscale.md covering Tailscale SSH, serve/funnel workflows, MagicDNS from containers, and MCP server setup
  • Behavioral Change: tailscale serve runs a background Tailscale process and post-verifies the endpoint via serve-status JSON, failing with exit code 1 if the requested local port is not confirmed; doctor now appends a Tailscale facts line to normal output

Macroscope summarized 6275e7c.

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

Prime Agent performance — completed

PR 6275e7cc compared with main aa242fb2.

Overall: 0 regressed · 0 improved · 41 no clear change.

Metric Main This PR Change
Cold startup 753.4 ms 770.0 ms ≈ +16.6 ms (+2.20%)
Warm startup 581.3 ms 496.2 ms ≈ -85.1 ms (-14.64%)
Installation 8.05 s 6.19 s ≈ -1.85 s (-23.02%)
Compressed release artifacts 73.04 MB 73.11 MB ≈ +0.06 MB (+0.09%)
Installed footprint 594.56 MB 594.60 MB ≈ +0.04 MB (+0.01%)
Idle memory, summed RSS 642.98 MB 644.63 MB ≈ +1.65 MB (+0.26%)

Python runtime

Metric Main This PR Change
Python kernel startup 35.2 ms 35.1 ms ≈ -0.092 ms (-0.26%)
Python cell round trip 0.099 ms 0.095 ms ≈ -0.005 ms (-4.57%)
Empty bash command 2.1 ms 2.1 ms ≈ -0.052 ms (-2.41%)
Bash git status 2.8 ms 2.6 ms ≈ -0.2 ms (-5.65%)
Bash 32 KiB output 2.0 ms 2.0 ms ≈ -0.0082 ms (-0.40%)
35 cells / 9 shell calls 27.3 ms 26.9 ms ≈ -0.4 ms (-1.62%)
Python interrupt to done 0.522 ms 0.526 ms ≈ +0.004 ms (+0.74%)
Python state snapshot 10.4 ms 10.0 ms ≈ -0.3 ms (-3.34%)
Python state restore 133.4 ms 136.7 ms ≈ +3.4 ms (+2.52%)
Python idle RSS 21.01 MB 21.00 MB ≈ -0.01 MB (-0.07%)
Python RSS after pandas workload 75.46 MB 75.51 MB ≈ +0.05 MB (+0.06%)

Session transport

Metric Main This PR Change
Private frame decode, 32 MiB in 8 KiB chunks 14.3 ms 15.2 ms ≈ +0.9 ms (+6.19%)

UI interactions

Metric Main This PR Change
Resume large session (cold) 1,694.0 ms 1,811.0 ms ≈ +117.0 ms (+6.91%)
CPU, resume large session 2,030.0 ms 1,950.0 ms ≈ -80.0 ms (-3.94%)
Switch into large session 1,711.7 ms 1,554.0 ms ≈ -157.6 ms (-9.21%)
CPU, switch into large session 2,030.0 ms 1,900.0 ms ≈ -130.0 ms (-6.40%)
Open agents view from a session 511.0 ms 469.2 ms ≈ -41.7 ms (-8.17%)
CPU, open agents view 890.0 ms 690.0 ms ≈ -200.0 ms (-22.47%)
Full agents roster, many sessions 4.03 s 4.04 s ≈ +0.0065 s (+0.16%)
CPU, full agents roster 0.95 s 0.97 s ≈ +0.02 s (+2.11%)
Open another session from agents view 1,954.7 ms 2,077.1 ms ≈ +122.3 ms (+6.26%)
CPU, open from agents view 1,080.0 ms 1,040.0 ms ≈ -40.0 ms (-3.70%)
Reopen resident large session 286.5 ms 267.4 ms ≈ -19.1 ms (-6.68%)
CPU, reopen resident session 320.0 ms 290.0 ms ≈ -30.0 ms (-9.38%)
Open subagent session at depth 6 17,611.2 ms 17,662.0 ms ≈ +50.8 ms (+0.29%)
CPU, open subagent at depth 6 4,580.0 ms 4,610.0 ms ≈ +30.0 ms (+0.66%)
Open chain parent from agents view 3,037.5 ms 3,168.8 ms ≈ +131.3 ms (+4.32%)
CPU, open chain parent 1,360.0 ms 1,440.0 ms ≈ +80.0 ms (+5.88%)
Scheduled catalog, first request 460.5 ms 514.8 ms ≈ +54.3 ms (+11.80%)
CPU, scheduled catalog 850.0 ms 970.0 ms ≈ +120.0 ms (+14.12%)
Scheduled catalog, repeated request 0.5 ms 0.4 ms ≈ -0.043 ms (-8.99%)
CPU, repeated catalog 0.0 ms 0.0 ms ≈ +0.0 ms (N/A)
Cold worker with three catalog scans 384.7 ms 461.6 ms ≈ +76.9 ms (+19.98%)
CPU, cold worker and scans 360.0 ms 400.0 ms ≈ +40.0 ms (+11.11%)
UI memory after interactions 1,957.14 MB 1,834.21 MB ≈ -122.93 MB (-6.28%)

Sandbox cost: ~$0.1017 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-21T20:19:10.817327+00:00. Harness aa242fb2.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Transport benches run node against the prepared source build, outside the installed home.
Frame decode times one 32 MiB private frame, snapshot-chunk header, pushed in
8 KiB chunks; the wire shape of multi-MB frames on the daemon-worker channels.
UI trials use a fresh fixture set: 194 top-level sessions including one ~40 MB transcript,
40 ledger fan-out children, and a 6-deep subagent chain (~46 spawn edges).
Large fixtures hold 1,999 complete triples (~5 MB JSONL); medium 119; subagents 399 each.
Interactions: cold --resume of a large session, warm /resume switch, left-arrow to agents view,
roster settle with many saved sessions, search-and-open of another large session,
reattaching to that resident session, opening the chain parent, and drilling to depth 6.
Readiness is the rendered transcript tail plus a confirmed editor echo.
CPU metrics sum utime+stime across the whole benchmark-user process tree per interaction.
UI memory sums RSS after the interactions; PTY byte counts are in the raw results.
A separate catalog fixture has 2,300 sessions, 2,298 edges, and 13 paused scheduled-job owners.
Catalog timings cover first/repeated reads and cold worker creation under three pending scans.
All expected jobs and owner metadata are checked; worker readiness excludes TUI rendering.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Performance changes are informational.
Failed or incomplete execution fails the workflow; saved artifacts remain available.
Each side stops a phase after 2 identical consecutive failures.
Skipped trials are not attempted samples. Warm startup requires a successful cold launch.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 10/10 IQR 395.1 ms IQR 147.6 ms
Warm startup 10/10 10/10 IQR 161.0 ms IQR 24.3 ms
Installation 3/3 3/3 range 2.42 s range 2.10 s
Compressed release artifacts 1/1 1/1
Installed footprint 1/1 1/1
Idle memory, summed RSS 10/10 10/10 IQR 2.07 MB IQR 3.82 MB
Python kernel startup 10/10 10/10 IQR 7.8 ms IQR 10.2 ms
Python cell round trip 10/10 10/10 IQR 0.037 ms IQR 0.071 ms
Empty bash command 10/10 10/10 IQR 0.4 ms IQR 0.2 ms
Bash git status 10/10 10/10 IQR 0.3 ms IQR 0.7 ms
Bash 32 KiB output 10/10 10/10 IQR 0.2 ms IQR 0.2 ms
35 cells / 9 shell calls 10/10 10/10 IQR 3.3 ms IQR 2.3 ms
Python interrupt to done 10/10 10/10 IQR 0.029 ms IQR 0.062 ms
Python state snapshot 10/10 10/10 IQR 0.6 ms IQR 0.8 ms
Python state restore 10/10 10/10 IQR 7.6 ms IQR 16.1 ms
Python idle RSS 10/10 10/10 IQR 0.18 MB IQR 0.15 MB
Python RSS after pandas workload 10/10 10/10 IQR 0.10 MB IQR 0.23 MB
Private frame decode, 32 MiB in 8 KiB chunks 10/10 10/10 IQR 2.2 ms IQR 3.2 ms
Resume large session (cold) 3/3 3/3 range 457.9 ms range 449.1 ms
CPU, resume large session 3/3 3/3 range 240.0 ms range 60.0 ms
Switch into large session 3/3 3/3 range 74.1 ms range 267.7 ms
CPU, switch into large session 3/3 3/3 range 100.0 ms range 210.0 ms
Open agents view from a session 3/3 3/3 range 67.8 ms range 61.1 ms
CPU, open agents view 3/3 3/3 range 200.0 ms range 170.0 ms
Full agents roster, many sessions 3/3 3/3 range 0.0072 s range 0.008 s
CPU, full agents roster 3/3 3/3 range 0.13 s range 0.18 s
Open another session from agents view 3/3 3/3 range 146.3 ms range 79.2 ms
CPU, open from agents view 3/3 3/3 range 130.0 ms range 120.0 ms
Reopen resident large session 3/3 3/3 range 35.7 ms range 55.5 ms
CPU, reopen resident session 3/3 3/3 range 40.0 ms range 10.0 ms
Open subagent session at depth 6 3/3 3/3 range 241.6 ms range 263.4 ms
CPU, open subagent at depth 6 3/3 3/3 range 180.0 ms range 150.0 ms
Open chain parent from agents view 3/3 3/3 range 109.3 ms range 189.9 ms
CPU, open chain parent 3/3 3/3 range 40.0 ms range 160.0 ms
Scheduled catalog, first request 3/3 3/3 range 19.2 ms range 114.3 ms
CPU, scheduled catalog 3/3 3/3 range 80.0 ms range 260.0 ms
Scheduled catalog, repeated request 3/3 3/3 range 0.061 ms range 1.3 ms
CPU, repeated catalog 3/3 3/3 range 0.0 ms range 0.0 ms
Cold worker with three catalog scans 3/3 3/3 range 57.8 ms range 134.2 ms
CPU, cold worker and scans 3/3 3/3 range 40.0 ms range 50.0 ms
UI memory after interactions 3/3 3/3 range 6.51 MB range 190.82 MB

@sethkarten
sethkarten enabled auto-merge (squash) September 18, 2026 06:12
Comment thread packages/coding-agent/src/cli/tailscale.ts Fixed
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten sethkarten changed the title [RSI] First-class Tailscale support (status, serve/funnel, doctor, docs) First-class Tailscale support (status, serve/funnel, doctor, docs) Sep 18, 2026
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch 2 times, most recently from 9d87c71 to 99b8beb Compare September 18, 2026 06:43
Comment thread packages/coding-agent/src/cli/public-command.ts Outdated
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 99b8beb to 75cac35 Compare September 18, 2026 07:42
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/docs/tailscale.md Outdated
Comment thread packages/coding-agent/docs/tailscale.md
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 75cac35 to 147f6ad Compare September 18, 2026 08:41
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
Comment thread packages/coding-agent/.changes/tailscale-support.md Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 147f6ad to 5170bc3 Compare September 18, 2026 09:12
Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts
…funnel, doctor, docs)

prime-agent tailscale: tailnet and MagicDNS detection with teaching
errors, serve --port [--funnel] wrapping tailscale serve/funnel for any
local bridge, a Tailscale section in doctor, and docs for the three
patterns (Tailscale SSH remote control, tailnet exposure, MagicDNS
reach from cloud containers + the official Tailscale MCP connector).
No new dependencies: shells out to the tailscale CLI detected at runtime.
@sethkarten
sethkarten force-pushed the rsi/tailscale-support branch from 5170bc3 to 295255b Compare September 18, 2026 09:44
@sethkarten

Copy link
Copy Markdown
Contributor Author

CORRECTION (process incident, transparency): replies at 147f6ad and 5170bc3 claimed some fixes that were lost from the tree before those commits were pushed. Audit of the pushed commits shows these claims were NOT in those commits but ARE now verifiably in 295255b (grep-verified in the pushed head):

  • ENOENT vs other-spawn-error taxonomy in probeTailscale (claimed at 147f6ad)
  • error-before-missing-CLI ordering in status and serve (claimed at 147f6ad)
  • single-probe --json (payload and exit code from the same probe; claimed at 147f6ad)
  • spawnSyncHidden at all spawn sites (claimed at 147f6ad)
  • killSignal: "SIGKILL" (claimed at 5170bc3; separate correction posted on that thread)
  • tailscaleStatusJson unexport (claimed at 5170bc3)

Root cause: a multi-step edit round where one edit step mutated the file in memory but was not persisted before an inspection step, and a later step re-read the file and wrote a tree missing the unpersisted change. Fixes that WERE correctly present at those commits: funnel AllowFunnel check, docs (userspace networking, Tailscale SSH prerequisites), changeset wording, host-suffix comment. All four of today's new findings trace exactly to the lost edits and are fixed at 295255b with tests green and the full check chain green. My process now greps the COMMIT content before posting any "fixed at " reply.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/coding-agent/src/cli/tailscale.ts
Comment thread packages/coding-agent/src/cli/tailscale.ts Outdated
sethkarten and others added 3 commits September 21, 2026 11:45
…lure; drop dead offline branch

Post-serve verification with unparseable serve status output now prints
the parse-failure line (matching the status command) and exits 1,
instead of the pending-enable warning for an enable flow that never
happened. The status command's !onTailnet branch dropped its
offlineButUp sub-branch: offlineButUp implies onTailnet, so an offline
node reaches the success path, which already prints its state.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6275e7c. Configure here.

const host =
probe.hostname.endsWith(`.${suffix}`) || probe.hostname === suffix
? probe.hostname
: `${probe.hostname}.${suffix}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invented MagicDNS suffix

Low Severity

After a successful serve, a missing magicDnsSuffix is replaced with ts.net and appended to the short node name. That prints a fabricated reachability host instead of omitting the URL the way a missing hostname already does.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6275e7c. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants