Skip to content

fix(#667): CLI no longer hangs on the receipt socket; resolve captains by socket path - #696

Merged
tu11aa merged 1 commit into
developfrom
fix/captain-channel-hang-and-cwd
Aug 19, 2026
Merged

fix(#667): CLI no longer hangs on the receipt socket; resolve captains by socket path#696
tu11aa merged 1 commit into
developfrom
fix/captain-channel-hang-and-cwd

Conversation

@tu11aa

@tu11aa tu11aa commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Two more defects from the slice 4 live smoke. Neither was reachable from unit tests.

1. squadrant ping delivered, then hung forever

The message arrived and the captain answered — and the CLI process never exited:

$ pgrep -fl 'index.js ping'
63345 node .../dist/index.js ping s4lab SLICE4-ALPHA...     # still resident, work long done

A listening server keeps Node's event loop alive. Any short-lived CLI that built a captain channel bound a receipt socket and could never terminate. Anything scripting squadrant ping would block indefinitely.

  • server.unref() after listen — the process can now exit once its own awaits settle. Safe for the daemon, which stays alive on its own control socket rather than this one.
  • stop() now also unlinks the socket file; close() does not, so every process that bound one was leaving a squadrantd-<pid>.sock behind in the shared directory.

2. Every captain ping reported accepted-unconfirmed

For a captain that was alive and answering:

captain-channel s4lab: accepted via claude-peer (unconfirmed — no turn observed)

The captain channel resolved the registry entry by cwd, and cwd is not canonical:

{"pid":44257,"cwd":"/private/tmp/s4lab","status":"idle",
 "messagingSocketPath":"/tmp/cc-socks/squadrant-captain-s4lab.sock"}

The project path is /tmp/s4lab; the session registers /private/tmp/s4lab because /tmp is a symlink on macOS. The raw string compare silently missed, statusFor returned undefined, and the confirmation branch never ran.

Added readClaudeStatusBySocketPath and switched the captain channel to it — the same identifier #689 adopted for crews: squadrant picks it at launch and the session echoes it back verbatim.

This removes the last cwd-based match from the delivery path. Slice 1 had already dropped cwd for crews because the operator's own Claude window shares the project root; non-canonical paths are a second, independent reason. Worth treating "never match a session by cwd" as settled.

Verification — slice 4 smoke now passes all four cases

Mode Result
unset/off unchanged, and the no-auto-boot refusal still fires
on ✔ Delivered to 's4lab' (accepted via claude-peer), returns in 8s, captain answered
shadow shadow probe — reachable, message delivered exactly once
gone (captain killed) session gone — falling back to pane⚠ not reachable, one fallback, both logged

6 new tests (unref, optional-unref, file cleanup, plus the earlier reject/unlink cases). pnpm build clean, 2660 tests green.

Related: #667 slice 4 (#685), #689, #691, #695.

…s by socket path

Two more defects found by the slice 4 live smoke, both invisible to unit tests.

1. `squadrant ping` delivered its message and then HUNG FOREVER. A listening
   server keeps Node's event loop alive, so any short-lived CLI that built a
   captain channel never exited (observed: process still resident, work complete).
   The server is now unref'd — safe for the daemon too, which stays alive on its
   own control socket. stop() also removes the socket FILE, which close() does
   not, so processes stop leaving squadrantd-<pid>.sock behind.

2. Every captain ping reported `accepted-unconfirmed` for a captain that was
   demonstrably alive and answering. The captain channel resolved the registry
   entry by cwd, and cwd is not canonical: a captain in /tmp/s4lab registers
   cwd "/private/tmp/s4lab" on macOS (/tmp is a symlink), so the string compare
   silently missed. Added readClaudeStatusBySocketPath and switched the captain
   channel to it — the same identifier #689 adopted for crews, chosen by
   squadrant at launch and echoed back verbatim by the session.

   This also removes the last cwd-based match from the delivery path. Slice 1
   already dropped cwd for crews because the operator's own window shares the
   project root; the non-canonical-path problem is a second, independent reason.

Live-verified after the fix, on a throwaway project:
  on      -> "✔ Delivered ... (accepted via claude-peer)", returns in 8s
  shadow  -> "shadow probe — reachable", message delivered EXACTLY once
  gone    -> "session gone — falling back to pane" then "⚠ not reachable",
             one fallback, logged both times
  off     -> unchanged, and the no-auto-boot refusal still fires

6 new tests. 2660 green.
@tu11aa
tu11aa merged commit b607042 into develop Aug 19, 2026
1 check passed
@tu11aa
tu11aa deleted the fix/captain-channel-hang-and-cwd branch August 19, 2026 09:15
@tu11aa tu11aa mentioned this pull request Aug 23, 2026
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.

1 participant