Skip to content

fix(bin): distinguish remote read deadlines from local ghost bounds - #6710

Open
apfelstrudels wants to merge 4 commits into
kunchenguid:mainfrom
apfelstrudels:fm/remote-reply-poll-bound
Open

apfelstrudels wants to merge 4 commits into
kunchenguid:mainfrom
apfelstrudels:fm/remote-reply-poll-bound

Conversation

@apfelstrudels

@apfelstrudels apfelstrudels commented Oct 6, 2026 •

Copy link
Copy Markdown

Intent

The captain asked for a hard timeout on the remote-reply poll connection: a ghost ssh (remote end died without closing, observed after herdr server restarts on the Mac mini) held the remote-reply mirror open for ~55 minutes, stalling mirrored status replies until killed by hand, even though the designed window is 55s. Fix: wrap the delta read in cmd_source of bin/fm-procevent-remote-reply.sh in fm_run_timed with a bound of the wait window plus 120s slack (FM_REMOTE_REPLY_BOUND_SECONDS override); on the bound firing, report a closed window exactly like a job preemption - no caught-up watermark, runner relistens, cursor-anchored read loses nothing. Second commit addresses review findings on PR #6710: distinguish the remote job worker's own execution deadline (also a timeout status) from a local ghost bound by elapsed time - a remote deadline exits as a failed read, never relistens; reject a non-positive FM_REMOTE_REPLY_BOUND_SECONDS loudly; and bound the ghost regression test with an outer 20s deadline so a broken bound fails promptly instead of stalling on the fixture's hour-long sleep. Includes colocated regression tests for both distinctions. Scope is deliberately limited to this adapter; other fm-on callers keep their own timeout contracts.

What Changed

  • cmd_source in bin/fm-procevent-remote-reply.sh now wraps the delta read with fm_run_timed under a hard BOUND_SECONDS wall-clock bound (WAIT_SECONDS + 120, overridable via FM_REMOTE_REPLY_BOUND_SECONDS), killing a ghost transport that never closes the link and reporting a closed window so the runner relistens, same as a job preemption - with a loud die if the override is non-positive.
  • Added elapsed-time logic to tell a local ghost-bound timeout apart from the remote job worker's own execution deadline (also surfaced as a timeout status): a deadline that fires before BOUND_SECONDS could have elapsed is treated as a failed read and returned as-is instead of being relistened.
  • Updated the header comment to describe the new ghost-bound relisten mapping, and sourced fm-timeout-lib.sh for the timeout helpers.
  • Added regression tests in tests/fm-remote-reply.test.sh covering both cases: a hung "ghost" read that gets killed at its bound and relistens without publishing a caught-up watermark (itself bounded by an outer 20s deadline so a broken bound fails fast), and a simulated remote execution deadline that exits as a failed read without relistening.

Risk Assessment

✅ Low: The change is scoped to one adapter, correctly wraps the delta read in fm_run_timed with a documented bound (WAIT_SECONDS+120, overridable), reuses the existing WINDOW_CLOSED_EMPTY/JOB_PREEMPTED relisten contract so a ghost bound behaves exactly like a preemption (no caught-up watermark), distinguishes a genuine remote-job execution deadline from the local ghost bound via an elapsed-time check with a correctly-reasoned 1-second truncation guard band, validates FM_REMOTE_REPLY_BOUND_SECONDS as a positive integer before use, and is covered by new tests that actually execute the adapter (not source-text assertions) for both the ghost-transport and remote-deadline cases, with an outer bound protecting the suite from stalling. I traced the remote worker's own timeout path (bin/fm-remote-job-worker.sh worker_run_with_timeout) and confirmed it does report 124 on its own deadline, validating the premise the elapsed-time disambiguation relies on.

Testing

Ran the colocated regression suite for bin/fm-procevent-remote-reply.sh end-to-end against the real script and fake-ssh fixtures; all 37 scenarios passed, including the two new ones for this change (ghost transport hits its hard bound and relistens without a watermark, in seconds thanks to the 20s outer test deadline; a remote job worker's own execution-deadline timeout is distinguished by elapsed time and exits as a failed read instead of relistening). The non-positive FM_REMOTE_REPLY_BOUND_SECONDS rejection had no existing test, so I drove it live with 0, -5, and abc and confirmed each exits 1 with the expected loud error message. No regressions, no missing evidence.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Ghost transport exceeds its hard bound and the poll relistens without a caught-up watermark ✅ pass live tests/fm-remote-reply.test.sh -> ok - a ghost reply poll dies at its hard bound and relistens without a watermark (fake ssh sleeps 3600s; fm_run_timed with BOUND_SECONDS=3 kills it, exits 75 in ~3s,…
Remote job worker's own execution deadline is distinguished from a local ghost bound and fails the read instead of relistening ✅ pass live tests/fm-remote-reply.test.sh -> ok - a remote read deadline exits as a failed read instead of relistening (fixture exits 124 immediately; elapsed time is under BOUND_SECONDS-1, so cmd_source return…
Existing job-preemption behavior (JOB_PREEMPTED) still relistens without a watermark ✅ pass live tests/fm-remote-reply.test.sh -> ok - a preempted reply poll reports a closed window without publishing channel freshness and `ok - a preempted reply poll keeps its listener and reconcile launches n…
Ghost regression test itself is bounded by an outer 20s deadline so a broken bound fails promptly instead of stalling on the fixture's hour-long sleep ✅ pass live Full suite (37 scenarios, several with real sub-second to multi-second sleeps) completed in well under 20s for the ghost scenario specifically, confirmed by suite log ordering and total wall-clock; fm…
Non-positive or non-numeric FM_REMOTE_REPLY_BOUND_SECONDS is rejected loudly rather than silently accepted ✅ pass live Manual live run: FM_REMOTE_REPLY_BOUND_SECONDS=0|-5|abc bin/fm-procevent-remote-reply.sh source ios each printed `error: FM_REMOTE_REPLY_BOUND_SECONDS must be a positive integer of seconds, got '<va…

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Ghost transport exceeds its hard bound and the poll relistens without a caught-up watermark ✅ pass live tests/fm-remote-reply.test.sh -> ok - a ghost reply poll dies at its hard bound and relistens without a watermark (fake ssh sleeps 3600s; fm_run_timed with BOUND_SECONDS=3 kills it, exits 75 in ~3s,…
Remote job worker's own execution deadline is distinguished from a local ghost bound and fails the read instead of relistening ✅ pass live tests/fm-remote-reply.test.sh -> ok - a remote read deadline exits as a failed read instead of relistening (fixture exits 124 immediately; elapsed time is under BOUND_SECONDS-1, so cmd_source return…
Existing job-preemption behavior (JOB_PREEMPTED) still relistens without a watermark ✅ pass live tests/fm-remote-reply.test.sh -> ok - a preempted reply poll reports a closed window without publishing channel freshness and `ok - a preempted reply poll keeps its listener and reconcile launches n…
Ghost regression test itself is bounded by an outer 20s deadline so a broken bound fails promptly instead of stalling on the fixture's hour-long sleep ✅ pass live Full suite (37 scenarios, several with real sub-second to multi-second sleeps) completed in well under 20s for the ghost scenario specifically, confirmed by suite log ordering and total wall-clock; fm…
Non-positive or non-numeric FM_REMOTE_REPLY_BOUND_SECONDS is rejected loudly rather than silently accepted ✅ pass live Manual live run: FM_REMOTE_REPLY_BOUND_SECONDS=0|-5|abc bin/fm-procevent-remote-reply.sh source ios each printed `error: FM_REMOTE_REPLY_BOUND_SECONDS must be a positive integer of seconds, got '<va…
  • bash tests/fm-remote-reply.test.sh (full adapter suite, 37 scenarios)
  • manual live invocation: FM_REMOTE_REPLY_BOUND_SECONDS=0|-5|abc bin/fm-procevent-remote-reply.sh source ios
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A poll whose remote end dies without closing the ssh link (observed after
a herdr server restart on macmini) held the remote-reply source open
indefinitely: the 55s window never closed, no relisten happened, and
mirrored replies stalled until the ghost was killed by hand.

Wrap the delta read in fm_run_timed with a bound of the wait window plus
120s of transport and queue slack (FM_REMOTE_REPLY_BOUND_SECONDS). A
bound hit reports a closed window exactly like a job preemption - the
cursor-anchored non-destructive read loses nothing, the runner relistens,
and no caught-up watermark is published.
@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds timeout logic to remote reply polling.

The PR appears safe to merge based on the findings in this review.

Reviews (3) · Last reviewed commit: "no-mistakes(ci): placeholder - will fina..."

Comment thread bin/fm-procevent-remote-reply.sh Outdated
Comment thread bin/fm-procevent-remote-reply.sh
Comment thread tests/fm-remote-reply.test.sh
Review on kunchenguid#6710 found three holes in the poll bound:

- A remote job worker execution deadline also surfaces as a timeout
  status, and was relistened as an empty window, hiding a failing read.
  cmd_source now separates the two by elapsed time: a timeout before the
  local bound could have fired is remote and exits as a failed read.
- FM_REMOTE_REPLY_BOUND_SECONDS=0 silently disabled the bound on the
  timeout and perl mechanisms; a non-positive override now fails loudly.
- The ghost regression could stall the suite for the fixture's hour-long
  sleep when the bound broke; an outer 20s deadline now bounds it, and a
  new case pins the remote-deadline distinction.
@apfelstrudels apfelstrudels changed the title fix(bin): bound the remote-reply poll against ghost transports fix(bin): distinguish remote read deadlines from local ghost bounds Oct 6, 2026
Comment thread bin/fm-procevent-remote-reply.sh
Comment thread tests/fm-remote-reply.test.sh
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: reviewed against main e06a46fe. Thanks, @apfelstrudels. This looks good. I approved the fork CI runs, so it's just waiting on CI.

What I checked: on main, cmd_source in bin/fm-procevent-remote-reply.sh runs fm-on.sh ... fm-remote-delta-read.sh ... "$WAIT_SECONDS" with no local bound, so a ghost ssh can hold the 55-second poll open indefinitely. This head wraps that one call in fm_run_timed "$BOUND_SECONDS" (WAIT_SECONDS + 120, overridable by FM_REMOTE_REPLY_BOUND_SECONDS). The override is validated as a positive base-10 integer, which also handles 08/09/00. A timeout that fires at the bound maps to WINDOW_CLOSED_EMPTY, exactly like JOB_PREEMPTED, and publishes no caught-up watermark. A timeout that arrives before BOUND_SECONDS - 1 elapsed is the remote worker's own deadline, and it returns as a failed read, as main already does for a 124. The read is cursor-anchored, so killing it loses nothing. The new tests cover the ghost bound (with an outer 20-second deadline and an elapsed-time check) and the remote-deadline path. No workflow files change. Head 9565bd66 matches the no-mistakes attestation. All 5 Greptile threads are resolved, and the latest Greptile summary on this head is 5/5.

CI: approved fork runs 37504936254 (CI) and 37504936396 (Require no-mistakes). Waiting on results.

Heads-up: #6708 also edits this file (cmd_ingest and retire). The hunks don't overlap today, but whichever lands second may need a rebase.

Contract-class: restore. The remote-reply poll is a specified bounded window (WAIT_SECONDS=55) that the runner relistens after. A transport that never closes breaks that existing default path. The bound restores it using the relisten contract preemption already has. It adds no new surface, wake class, or observer.

VISION.md, rule by rule

  • One captain, one interface: aligns. Mirrored replies stop stalling silently for most of an hour.
  • Authority is explicit and never inferred: aligns. No new autonomy.
  • Scripts own the mechanics, agents own the judgment: aligns. A wall-clock bound plus an elapsed-time split is exact script work.
  • A restart is a non-event: aligns. Cursor-anchored and non-destructive, so a killed poll loses nothing.
  • Delegation with a spine: aligns. No change to task contracts.
  • The fleet outlives any vendor: aligns. Uses the shared fm-timeout-lib.sh, which also covers macOS without coreutils.
  • Scope: aligns. Command-layer relay. A field incident becomes regression coverage.

Next step: once CI is green on this head, it's eligible to merge as restore on the next pass.

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.

3 participants