Skip to content

cli: classify broker control-channel closure vs dial refusal - #194

Merged
ysyneu merged 2 commits into
feat/broker-channel-errorsfrom
fix/broker-channel-errors
Sep 15, 2026
Merged

ysyneu merged 2 commits into
feat/broker-channel-errorsfrom
fix/broker-channel-errors

Conversation

@ysyneu

@ysyneu ysyneu commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Why

The broker-mode dialer collapsed two distinct handshake failures into one opaque message:

  • a dead control channel (the runner exited, or reclaimed the channel after the command that started this process finished) surfaced as broker handshake send: broken pipe or the misleading broker refused connection (code []) when the recv returned EOF;
  • a live broker declining the dial (the 0xFF refusal byte) used the same "refused" wording.

What

  • New exported sentinel ErrBrokerClosed. Sendmsg EPIPE/ECONNREFUSED and Recvmsg EOF (n==0) now wrap it, so callers and automation can classify "broker gone" programmatically via errors.Is — which keeps working through the http.Transport/url.Error wrapping, and the user-visible message now explains that the broker that started the process is no longer available.
  • The 0xFF refusal gets its own clear message (broker refused the dial request) and is deliberately not ErrBrokerClosed; the bogus code [] formatting is gone, and any other unexpected response byte is reported as an explicit hex byte.
  • New dialer-level tests cover recv-EOF, send-EPIPE, and refusal classification; the existing end-to-end broker tests (handshake + SCM_RIGHTS dispatch, app_key rewrite, concurrency, refusal) keep passing.

Testing

  • go test -race ./... (darwin)
  • GOOS=linux go build ./... + GOOS=linux go vet ./internal/cli/ (compiles the unix-tagged test files for linux too)
  • make (gofmt + gci + golangci-lint 0 issues + race tests + build)

Known limitation (pre-existing, out of scope)

The handshake has no deadline: if the broker is alive but never answers a handshake request, the dial blocks until the caller's context/HTTP timeout fires. Unchanged by this PR.

The broker dialer collapsed two distinct handshake failures into one
opaque message. A dead control channel (runner gone, or channel
reclaimed after the spawning command finished) surfaced as "broker
handshake send: broken pipe" or the misleading "broker refused
connection (code [])" on recv EOF, while a live broker declining the
dial (0xFF byte) used the same "refused" wording.

- Add exported sentinel ErrBrokerClosed; Sendmsg EPIPE/ECONNREFUSED and
  Recvmsg EOF (n==0) now wrap it, so errors.Is keeps working through
  the http.Transport/url.Error wrapping.
- The 0xFF refusal gets its own clear message and no longer prints a
  bogus "code"; other unexpected response bytes get an explicit hex
  byte instead.
- Add dialer-level tests for EOF, EPIPE, and refusal classification.
- Demote ErrBrokerClosed to unexported errBrokerClosed: the SDK flattens
  dial errors into text, so error identity never survives end-to-end and
  an exported sentinel has no consumer. Classification rides the
  user-visible message; only in-module tests use errors.Is.
- Drop the "flashduty: " prefix from the new messages (the SDK adds it),
  and give the 0xFF refusal its final actionable wording.
- Fix "broker parse rights: %w" rendering %!w(<nil>) when the rights
  parse succeeds but yields no fd.
- Reword comments to state production errno behavior (Linux SEQPACKET
  send -> EPIPE; datagram-style peer death -> ECONNREFUSED) and note the
  sentinel covers the handshake phase only.
@ysyneu
ysyneu merged commit a4e01c0 into feat/broker-channel-errors Sep 15, 2026
12 checks passed
@ysyneu
ysyneu deleted the fix/broker-channel-errors branch September 22, 2026 10:03
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