Skip to content

fix(bin): bound herdr CLI probes so a hung read cannot wedge a supervisor - #4988

Open
keenvc wants to merge 1 commit into
kunchenguid:mainfrom
keenvc:fm/firstmate-herdr-probe-timeout
Open

keenvc wants to merge 1 commit into
kunchenguid:mainfrom
keenvc:fm/firstmate-herdr-probe-timeout

Conversation

@keenvc

@keenvc keenvc commented Sep 20, 2026

Copy link
Copy Markdown

Problem

Herdr capture/composer_state-style supervisor probes (and every other synchronous herdr CLI read) ran with no bounded read timeout. A wedged herdr server or a hung pane read blocked the calling supervisor indefinitely and leaked one stuck shell per occurrence instead of failing loudly and freeing the caller.

Fix

Every synchronous herdr CLI read/write in bin/backends/herdr.sh now runs under a real process-level bound through the repo-wide bounded runner bin/fm-timeout-lib.sh (fm_run_timed), which kills the whole child process group and reports the bound as exit 124.

  • New FM_BACKEND_HERDR_CLI_TIMEOUT (default 10s, invalid/zero falls back to 10) plus a fm_backend_herdr_bounded helper.
  • Bounded call sites: fm_backend_herdr_cli (the single owner every capture, composer-state, busy-state, pane/agent read, and write goes through), its protocol-mismatch retry, fm_backend_herdr_client_status, fm_backend_herdr_version_check, fm_backend_herdr_resolve_bare_selector, fm_backend_herdr_socket_path, and fm_backend_herdr_events_capable.
  • The long-lived herdr server launch stays exempt: its purpose is to outlive the call and a bound would kill the server. A regression test pins that exemption so it cannot be bounded by accident.

Audit

bin/backends/herdr.sh is the only supervision path that calls herdr: fm_backend_capture, fm_backend_composer_state, fm_backend_busy_state, and the recovery-grade liveness reads all dispatch into it. The remaining direct herdr invocations in the repo are outside ordinary supervision (fm-remote-doctor.sh, fm-herdr-ci-cleanup.sh, fm-install-herdr.sh, fm-remote-herdr-guard.sh, fm-herdr-lab.sh), and the event subscriber (bin/backends/herdr-eventwait.py) keeps its own bounded socket reader.

Test

tests/fm-backend-herdr-probe-timeout.test.sh fakes a herdr that ignores TERM and never answers a read, then asserts for capture, composer-state, and the generic CLI owner that the call returns within the bound and the recorded process is gone (the KILL escalation reaps a TERM-ignoring child). It also asserts the server launch is not bounded. Portable, no real herdr required.

Verification

  • bash tests/fm-backend-herdr-probe-timeout.test.sh - 4/4 ok
  • bash tests/fm-backend-herdr.test.sh - all ok
  • bash tests/fm-herdr-session-cleanup.test.sh, fm-busy-state, fm-task-inbox, fm-send-strict, fm-send-resolve-key, fm-agy-harness, fm-busy-adapter-wiring - all ok
  • bin/fm-lint.sh bin/backends/herdr.sh bin/fm-test-run.sh tests/fm-backend-herdr-probe-timeout.test.sh - exit 0
  • bin/fm-test-run.sh --check-coverage - ok total=219
  • bin/fm-doc-audience-check.sh - ok

Docs

docs/herdr-backend.md "Current transport behavior" and docs/configuration.md document the bound, its default, and the server exemption.

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