Skip to content

bb status exits 0 against a dead daemon and hangs with zero bytes against a stalled one #2382

Description

@MGrin

Summary

bb status reports success against a daemon that is not running, and hangs with zero bytes against one that is stalled. Both builtins commonly reached for as a health check (bb status, bb plugin list) fail on the stalled case, while the plugin command path is the only surface that is self-describing when the daemon cannot answer the probe.

Correction (2026-08-25, see comment below): an earlier version of this sentence claimed the plugin path was bounded in every failure state. That is withdrawn — it generalised from a fixture that only stalled the probe. runPluginCliCommand's POST /api/v1/plugins/:id/cli has no timeout, so once the probe succeeds and the daemon stalls after it, bb tasks list also hangs with zero bytes. No bb command is bounded.

Measured on bb 0.39.0, macOS 15 (Darwin 25.5.0), 2026-08-25T06:45:20Z–06:46:37Z.

Reproduction

Point the CLI at a URL where nothing is listening:

BB_SERVER_URL=http://127.0.0.1:<closed-port> bb status

Expected: non-zero exit and an error saying bb is unreachable.
Actual: rc=0 in 0.22s, printing e.g. Project: proj_xxxxxxxxxx — values read from local config/env, with no connection ever made.

Full matrix

Three daemon states, exercised against a fake daemon so each state is controlled. n=10 for the hang cases.

daemon state bb status bb plugin list bb tasks list (plugin path)
dead — nothing listening rc=0, 0.22s, prints project rc=1, 0.22s, Cannot connect to BB server rc=1, 0.21s, bb is not running at <url>
stalled — accepts, never answers hangs, 0 bytes, killed at 25s (10/10) hangs, 0 bytes, killed at 25s (10/10) rc=1, 10.85s, bb did not respond … after 3 attempts (last window 4000ms)
answering, broken rc=1, c.childThreads.map is not a function rc=1, Error: [ rc=1, unknown command 'tasks'

Mechanism

Observed by logging the fake daemon's inbound requests:

  • bb plugin list issues one unbounded GET /api/v1/plugins — no AbortSignal, no timeout, no retry. A daemon that accepts the socket and never responds hangs the CLI indefinitely.
  • The plugin command path issues three bounded GET /api/v1/plugins/contributions (2s / 4s / 4s windows, with 150ms and 500ms backoff), then reports how many attempts it made.

So the retry work added for the plugin path (#1313) gave that path a property the builtins lack.

Why the rc=0 case matters most

bb status is widely treated as the safe liveness probe because it skips the plugin-contribution probe. That advice is in our own machine documentation, and we found it in 8 separate places including an installer's install-time health check on a fresh machine — which means an install can report bb healthy against no daemon at all.

An rc=0 that never touched the network is indistinguishable from a genuine success, so nothing downstream can detect it.

Suggested fixes, in the order we'd value them

  1. bb status should fail non-zero when it cannot reach the daemon. If printing cached/local config without a connection is intentional, please make it explicit — a separate flag, or a line in the output stating the daemon was not contacted.
  2. Bound bb plugin list's request with a timeout, so it errors rather than hanging.
  3. Optionally, document which commands actually contact the daemon. The builtin/plugin split does not currently predict it.

Workaround we adopted

timeout 20 bb plugin list — never bare, since unwrapped it hangs on exactly the condition you reach for it in. We removed bb status from our docs as a health probe entirely.


Happy to supply the fake-daemon harness used for the matrix if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    clibb CLI

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions