Skip to content

feat(fleet): evaluate a tailnet-bound opencode serve option on the laptop for attach clients #1363

Closed

Description

@dezren39

From the access-pattern benchmark (code#1344): both laptop opencode servers (background service :13420, OpenChamber's :37621) bind 127.0.0.1 only, so every network attach pays for an SSH forward and each call carries the DERP RTT (~0.45s). The stack already has a tailnet-bind precedent (pm-aggregator binds 100.99.113.103:4870). Server password auth is enforced (401 unauthenticated), so binding is the only missing piece.

Evaluate (design + small trial, not a config flip): a second, tailnet-bound serve instance or a tailscale-serve-style relay for the background service, restricted to the tailnet interface, password auth on, CORS/origin implications checked against OpenChamber's remote-instance flow. Measure the per-call delta vs the forward (expect RTT-bound improvement only if a direct path exists; DERP hairpin may erase it — measure, don't assume).
Acceptance: a written verdict with measurements; if favorable, the binding ships behind the laptop's normal config flow with the access pattern documented in the benchmark report's terms.

Acceptance criteria

  • A written verdict exists (issue comment or linked doc) on whether a tailnet-bound serve instance or tailscale-serve-style relay is viable for the laptop background service, with password auth on and CORS/origin implications for OpenChamber's remote-instance flow checked.
  • The per-call latency delta vs the SSH-forward path is measured (not assumed), including whether a direct tailnet path exists or DERP hairpin erases the gain.
  • If the verdict is positive, a follow-up implementation issue is filed; if negative, the reasoning is recorded on this issue and it is closed.

Activity

  1. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Direction confirmed by Drew (2026-10-27 03:37 CDT): use Tailscale so no SSH connection is needed for the opencode link. Raised to Horizon Today. Preferred shape: keep the laptop server loopback-only and publish it to the tailnet with tailscale serve (tailnet-only exposure, MagicDNS name, no sshd / no forward process in the path), with the server password auth still enforced and Tailscale ACLs scoping which devices can reach the port. Cloudflare Tunnel was considered and set aside for this link: the sandbox is already on the tailnet, and a Cloudflare route would add a public hostname + Access layer + edge latency in front of a command-executing API without buying anything the tailnet doesn't already provide. The persistent SSH forward remains the interim pattern until this lands; on completion, the fleet job bodies (stewardship-cycle, jules-review, orchestration-pass) get a one-line pattern update.

  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    Follow-up from the #1344 benchmark: both laptop opencode servers bind 127.0.0.1 only, so network attach pays SSH-forward + DERP RTT (~0.45s/call); a tailnet-bind precedent exists (pm-aggregator). This is an evaluation, not a config flip. Risk: binding wider changes the service's exposure surface — auth is enforced (401 unauthenticated) but the trial must stay tailnet-interface-only. Suggested approach: small timed trial comparing forward vs tailnet path on the same calls, then a written verdict.

    Related: Related: #1344 (access-pattern benchmark this follow-up came from). Labels normalized by fleet monitor: the issue arrived with only meta/follow-up.

    Still live: checked this run — no merged PR in the recent merged list of this repo implements or resolves this issue, and no closing reference was found; the issue stays open.

    🤖 Agent note · filed

  4. added
    kind/featureNew feature or capability request
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 27, 2026
  5. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    RESULT (2026-10-27, second attempt — the first died in a daemon restart after configuring serve but before verifying):

    Laptop / tailnet side: DONE and verified.

    • Route: tailscale serve HTTPS — https://amd-6.raptor-pumpkinseed.ts.net (tailnet only) → proxy http://127.0.0.1:13420. The opencode server itself was NOT rebound; it still listens on laptop loopback only. No sudo was needed; the serve config is user-level and reversible.
    • Verified end-to-end through the route: unauthenticated → 401 (password auth enforced); authenticated GET /api/session → 200 with the live session list; authenticated directory-scoped GET /api/provider → 200.
    • Latency (laptop-local, median of 7): direct backend 1.4 ms vs via serve 16.1 ms — serve adds ~15 ms; a real tailnet client adds one network RTT on top, still far below the SSH forward's ~470 ms median from the research(fleet): opencode via SSH-on-laptop vs attaching to the laptop server over the network — which is more efficient for opencode work? #1344 benchmark.
    • Posture: serve listeners exist only on the tailscale addresses; direct probe of the tailnet IP on :13420 refused; nothing on the laptop's LAN interfaces; every serve entry marked "(tailnet only)". Tailnet ACL policy was not changed (admin console) — any device on the tailnet can reach the port, and the opencode password remains the gate.
    • A temporary raw-TCP serve port (13421) added during testing was removed again; final serve state = the HTTPS route + the pre-existing forwards (18742–18746, 3000).

    Sandbox side: BLOCKED by the sandbox platform — keeping this issue open for that half.
    The sandbox cell cannot use the route, for platform reasons unrelated to the laptop or Tailscale:

    • The cell is not a real tailnet node (no tailscale interface; its tailscale binary is a platform shim reporting "enrolled"; tailnet IPs route via the cell gateway and raw TCP to them is "Operation not permitted").
    • DNS for the tailnet name resolves to a fake IP (198.18.161.121) owned by the egress stack.
    • The only egress is the Hatch egress proxy, which MITMs TLS (the serve cert presents as issued by "Hatch Sandbox Egress CA"). Through it, CONNECT + TLS complete, but every HTTP request to every serve port returns an empty reply (curl exit 52) — including the pre-existing OpenChamber :3000 serve, which long predates this change. SSH works only via the purpose-built CONNECT relay.
    • So: any genuine Tailscale client can use this route today with no SSH. The sandbox cannot until the platform gives the cell a real tailnet path or the proxy passes these tunnels. The SSH-forward pattern stays as the sandbox's route meanwhile; the attach-first helper (feat(fleet): attach-first opencode client helper — forward lifecycle, service discovery, stdin-safe run wrapper #1357) should treat the serve URL as the preferred base URL whenever the client is a real tailnet node.

    Full write-up: hub goal doc files/opencode-access-benchmark-2026-10-27.md, "Tailscale route" addendum. Follow-on filed for the sandbox-platform half.

  6. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    CORRECTION + COMPLETION (2026-10-27, verified directly by Muse after Drew pushed back on the earlier "sandbox blocked" conclusion): the sandbox CAN use this route. The earlier blocked verdict was a testing error — calls were made through the default egress proxy (port 3128, which MITMs TLS and refuses tailnet), not through the documented TAILNET proxy route (the runtime proxy host on port 3130; ~/docs/devices/tailscale.md; the same route the fleet-feed collector has used every ~5 minutes all along).

    Fresh sandbox-side verification (2026-10-27 09:23 CDT):

    • Aggregator via 3130 proxy → HTTP 200 (http://100.99.113.103:4870/api/health).
    • This issue's serve route via 3130 proxy → HTTP 401 at https://amd-6.raptor-pumpkinseed.ts.net/api/session — reachable, opencode password auth enforced. That is the end-state: from the sandbox, no SSH connection and no forward process in the data path.
    • Direct probe of the laptop tailnet IP :13420 still refused — correct: the server remains loopback-only; tailscale serve is the door.

    Closing as complete. The fleet job bodies are being updated to the serve-URL pattern (stewardship-cycle, jules-review, orchestration-pass) and #1357's helper scope now wraps this route instead of SSH-forward lifecycle. #1382 (filed on the wrong blocked diagnosis) is closed as invalid.

  7. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Kill switch for the stranded opencode service worker — shipped

    Follow-up to the serve remap: browsers that visited this origin while it fronted the opencode app still hold opencode's Workbox service worker, which served the opencode shell from precache after the remap (Drew, 2026-10-27: "I shouldn't need to do that" -- re clearing site data by hand).

    Change — PR #1434, merged (95cd41f): the aggregator now answers GET /sw.js with a tiny self-unregistering service worker (skipWaiting on install; on activate it unregisters itself and navigates controlled window clients to their current URL), Content-Type: text/javascript, Cache-Control: no-cache. One route + one script constant in pm-stack/aggregator/src/server.ts, plus a boot test.

    Tests — bun test: 273 pass / 0 fail; bun run typecheck: clean.

    Deploy — install.sh from the merged tree, then an explicit systemctl --user restart pm-aggregator.service (the #1332 wart: install alone left the old process, PID 1534255 from 06:15, running; after the restart the new process, PID 1926724, started 10:40:13 CDT).

    Verified from the laptop through tailscale serve:

    Healing mechanics: a stranded browser picks the new /sw.js up on its next service-worker update check (browsers revalidate /sw.js on navigation and at least every 24h; the no-cache header keeps that reliable). When it activates, the registration removes itself and open tabs reload into the dashboard -- no user action, no site-data clearing.

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

Metadata

Metadata

Assignees

Labels

effort/mMedium: expected under 1 day of workkind/featureNew feature or capability requestmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedmeta/follow-upFollow-up spun out of earlier workpriority/normalOrdinary priority (the default)status:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions