Summary
The Amicode extension always spawns its own opencode serve (server_manager.ts — there is no attach-only path). On a fleet client machine, the intended design is that the panel reaches the canonical server through an SSH local-forward (amicode.opencodePort: 4096). That only works by accident: the spawned server must lose the port race (EADDRINUSE) so the health probe ends up riding the tunnel.
On 2026-08-07 we found the MacBook client had been running a forked chat server for ~3 weeks (279 sessions, Jul 20 → Aug 7), invisible to the canonical server, culminating in a user-facing "can't do chats" when the fork's per-boot OPENCODE_SERVER_PASSWORD stranded the panel on 401s.
Root cause chain
- Extension spawns
opencode serve --port=4096 unconditionally when a binary is present (extension.ts boot → ServerManager.start()).
- The client's tunnel (
ssh -N amico-mini, LocalForward 4096 127.0.0.1:4096) bound IPv6 [::1]:4096 only — OpenSSH resolves localhost to ::1 first on macOS and binds the first address that works.
- IPv4
127.0.0.1:4096 stayed free → every VS Code launch spawned a fork that won the race. The panel (IPv4-first DNS) talked to the fork ever after.
- Nothing detects this: the health probe is satisfied by any 200 on the port, so a forked server reads as healthy.
Immediate mitigations applied (ops-side)
- Client ssh config now binds explicitly:
LocalForward 127.0.0.1:4096 127.0.0.1:4096.
- Fork killed, tunnel restarted; forked history merged into canonical (collision-checked, position-guarded on the
event stream); failure mode + merge nuances recorded in the fleet playbook.
These are fragile: if the tunnel is ever down when VS Code launches, the extension silently forks again.
Proposed fix
An attach-only client mode for the extension, e.g.:
amicode.serverMode: "spawn" | "attach" (default "spawn"), or
- when
amicode.opencodePort is set and a healthy server already answers on it with the expected identity, attach instead of spawning — and if nothing answers, fail closed (chat disabled with a clear message), never spawn on a client.
Concretely in ServerManager: probe http://127.0.0.1:<port>/health (or an identity endpoint) before spawning; in attach mode, skip cp.spawn entirely and surface a distinctive status when the tunnel is down.
Also worth considering
- A server identity handshake (canonical server reports a fleet/instance id; the extension warns when the answering server's id ≠ expected) so any future fork is loudly detected instead of silently used.
- Documenting that the per-boot server password + stale restored webviews present as "401 on everything" after a respawn — that symptom sent the user hunting in the wrong place first.
Incident reference
Fleet recovery of 2026-08-07: canonical DB 634 → 637 sessions after merging the fork (3 recovered sessions, incl. one that was itself troubleshooting this bug — meta). Recovery artifacts: ~/.amico/fleet-recovery/2026-08-07/ on the server.
Summary
The Amicode extension always spawns its own
opencode serve(server_manager.ts— there is no attach-only path). On a fleet client machine, the intended design is that the panel reaches the canonical server through an SSH local-forward (amicode.opencodePort: 4096). That only works by accident: the spawned server must lose the port race (EADDRINUSE) so the health probe ends up riding the tunnel.On 2026-08-07 we found the MacBook client had been running a forked chat server for ~3 weeks (279 sessions, Jul 20 → Aug 7), invisible to the canonical server, culminating in a user-facing "can't do chats" when the fork's per-boot
OPENCODE_SERVER_PASSWORDstranded the panel on 401s.Root cause chain
opencode serve --port=4096unconditionally when a binary is present (extension.tsboot →ServerManager.start()).ssh -N amico-mini,LocalForward 4096 127.0.0.1:4096) bound IPv6[::1]:4096only — OpenSSH resolveslocalhostto::1first on macOS and binds the first address that works.127.0.0.1:4096stayed free → every VS Code launch spawned a fork that won the race. The panel (IPv4-first DNS) talked to the fork ever after.Immediate mitigations applied (ops-side)
LocalForward 127.0.0.1:4096 127.0.0.1:4096.eventstream); failure mode + merge nuances recorded in the fleet playbook.These are fragile: if the tunnel is ever down when VS Code launches, the extension silently forks again.
Proposed fix
An attach-only client mode for the extension, e.g.:
amicode.serverMode: "spawn" | "attach"(default "spawn"), oramicode.opencodePortis set and a healthy server already answers on it with the expected identity, attach instead of spawning — and if nothing answers, fail closed (chat disabled with a clear message), never spawn on a client.Concretely in
ServerManager: probehttp://127.0.0.1:<port>/health(or an identity endpoint) before spawning; in attach mode, skipcp.spawnentirely and surface a distinctive status when the tunnel is down.Also worth considering
Incident reference
Fleet recovery of 2026-08-07: canonical DB 634 → 637 sessions after merging the fork (3 recovered sessions, incl. one that was itself troubleshooting this bug — meta). Recovery artifacts:
~/.amico/fleet-recovery/2026-08-07/on the server.