Skip to content

fix(cli): fail fast when the broker credential fd is not inherited - #211

Merged
ysyneu merged 1 commit into
mainfrom
fix/broker-fd-preflight
Oct 9, 2026
Merged

ysyneu merged 1 commit into
mainfrom
fix/broker-fd-preflight

Conversation

@ysyneu

@ysyneu ysyneu commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Problem

In broker mode fduty talks to Flashduty through a control socket that the runner passes as an inherited fd (FLASHDUTY_CRED_FD). Callers that spawn fduty without passing inherited fds close or reuse that fd:

  • Python subprocess (close_fds=True by default)
  • Node child_process
  • sudo

Until now this failed only at the first request, and the cause came last, after the SDK's request URL:

Error: flashduty: request to http://flashduty.broker.local/...?app_key=... failed: Post "http://flashduty.broker.local/...": broker handshake send: bad file descriptor

Nothing in the message says how to fix it.

Change

newBrokerHTTPClient now checks getsockopt(SOL_SOCKET, SO_TYPE) on the fd before it builds the client. If the fd is not an open socket, it returns this error:

Error: FLASHDUTY_CRED_FD=3 is not an open socket in this process (bad file descriptor): the program that started fduty did not pass the credential channel down. Run fduty from the shell, or keep fd 3 open when spawning it: Python subprocess.run(cmd, pass_fds=(3,)); Node: set entry 3 of spawn's stdio array to 3

The function now returns (*http.Client, error). As a result:

  • the non-unix stub returns errBrokerUnsupported directly, and that sentinel is defined only in broker_dial_other.go;
  • root.go no longer needs its nil check.

Verification

  • make passes: golangci-lint reports 0 issues, go test -race ./... passes, and the build succeeds.
  • GOOS=linux go vet and GOOS=windows go build both pass.
  • internal/cli tests pass on Linux (SOCK_SEQPACKET) in a python:3.12-slim container.
  • A new test, TestDefaultNewClient_CredFDNotInherited, covers a closed fd and a regular-file fd.
  • In the same container, a Python subprocess.run(["fduty", ...]) call with default arguments now prints the error above as its first stderr line. With pass_fds=(N,), the handshake reaches the control socket.

In broker mode fduty reaches Flashduty through a control socket the
runner passes as an inherited fd (FLASHDUTY_CRED_FD). Programs that spawn
fduty without passing inherited fds - Python's subprocess (close_fds=True
by default), Node's child_process, sudo - leave that fd closed or reused
by an unrelated file. The failure then only surfaced at the first request
as "broker handshake send: bad file descriptor" (or "socket operation on
non-socket"), after the SDK's request URL prefix, with no hint of the fix.

newBrokerHTTPClient now checks that the fd is an open socket before
building the client and returns an error that names the fd and how to
keep it open (run from the shell, Python pass_fds, Node stdio entry). It
returns (*http.Client, error), so the non-unix stub returns
errBrokerUnsupported directly and root.go drops its nil check.
@ysyneu
ysyneu merged commit 641c52c into main Oct 9, 2026
12 checks passed
@ysyneu
ysyneu deleted the fix/broker-fd-preflight branch October 9, 2026 03:28
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