Skip to content

fix(bin): stop teardown rebuilding retired homes and bound e2e waits by wall clock - #2009

Open
sbracewell64 wants to merge 3 commits into
kunchenguid:mainfrom
sbracewell64:fm/fork-trunk-serial2-base-red
Open

fix(bin): stop teardown rebuilding retired homes and bound e2e waits by wall clock#2009
sbracewell64 wants to merge 3 commits into
kunchenguid:mainfrom
sbracewell64:fm/fork-trunk-serial2-base-red

Conversation

@sbracewell64

Copy link
Copy Markdown

Intent

Fix the fork trunk's own red CI check "Behavior portable serial 2", which every open fork PR inherits and which therefore blocks the entire merge queue (nine parked fork PRs).

Measured evidence at intake: tests/fm-remote-secondmate-lifecycle-e2e.test.sh failed with "not ok - first inheritance transaction never reached its blocked write" on the base commit ed376cf itself; two independent lanes reproduced it, one proving its diff was a single doc file with bin/ and tests/ byte-identical to base and still red; the last ten fork-main CI runs all concluded failure. Required method: reproduce locally at the fork trunk first, diagnose the actual mechanism with disconfirming evidence for the losing hypothesis (test-side environment/timing/CI-skew versus shipped code), fix the real cause, prove a test-side fix with a witnessed-red control and ship a regression test with any code-side fix, and state exactly why the same run goes green in CI - pinning or neutralizing any load-bearing CI-environment difference rather than hoping. Two named hypotheses to check against the evidence: the CI treehouse v2.0.1 pin versus local v2.1.x, and the recorded serial lane running near its own timeout. Keep the change minimal and provable. This touches firstmate's shared tracked material, so the firstmate-coding-guidelines skill applies.

Findings and decisions made while doing the work:

DEFECT 1 (test-side). The config-push inheritance barrier bounded its wait by a poll count (250 iterations of sleep 0.02) rather than a duration. On the CI runner those iterations elapse in 5.3 seconds while the transaction needs longer to traverse its SSH-boundary jobs - the sibling spawn barrier in the same CI job took about 16 seconds. Git history shows upstream raised exactly this bound to 1500 in upstream #1727 (commit 30b18b9, 2026-08-04) and the fork reconciliation ad53e97 (2026-08-05) landed 250 at that one site while keeping 1500 at its sibling: a dropped upstream fix, not a code fault. Disconfirming evidence against a code-side defect: with an enlarged bound the transaction does reach its blocked write (measured 297-341 iterations / 19-26 seconds across three loaded runs) and every serialization assertion after it passes.

DELIBERATE CHOICE: rather than restoring upstream's literal 1500 (a one-line change that would minimize fork/upstream drift), the fix converts these waits to wall-clock bounds via a new shared tests/lib.sh helper, fm_test_wait_file. Rationale: a poll count is not a duration - it shrinks precisely when the work it waits for is slowest, so a restored count leaves the same defect one loaded runner away, and the same reconciliation could drop it again. The helper mirrors bin/fm-remote-job-lib.sh, which already bounds its own polls by wall clock. All six blocked-worker waits in that suite use it: 90 seconds for a remote transaction, 30 for a local marker, chosen as hang tripwires with margin over the measured 26-second worst case rather than expected durations, since a healthy wait ends the moment its marker appears. The helper also distinguishes a producer that died from a bound that expired, and re-checks the marker after observing a dead pid so a producer that writes and exits is not misreported.

DEFECT 2 (code-side), only reachable once the wait was fixed. The suite then failed at its final case: a remote secondmate retirement completed, reported success, and left the retired home behind as a stray tree containing data/.parent-route/wake-ledger.tsv. Mechanism: a remote secondmate is retired by a host-local teardown whose DATA is a private directory INSIDE the home being removed (bin/fm-remote-secondmate-control.sh), the terminal wake-ledger line is written after that removal, and bin/fm-wake-ledger.sh creates its ledger's directory - so the telemetry write rebuilt the tree the retirement had just deleted. Confirmed by a direct probe of the ledger writer against a non-existent destination, and isolated as fork-specific by running the upstream trunk on the same loaded machine, where the same suite passes (bin/fm-wake-ledger.sh does not exist upstream).

DELIBERATE CHOICE on the shape of that fix: an earlier version moved the ledger write ahead of the home removal, which also fixed a latent metadata-join degradation. That was rejected because it changed teardown's failure semantics - a teardown that refuses after the removal would then have already emitted a terminal "landed" line, and a retry would append a second one. The shipped fix instead leaves the write in place and skips only a destination inside a home this teardown just removed, tested both by path containment and by the vanished directory so neither spelling of the path resurrects it. The skipped line was unreachable evidence in that shape anyway: it lived only inside the deleted home, and the parent home exits before its own ledger write for a remote retirement.

Verification performed: every new guarantee was witnessed red under a matching defect before being accepted - a fixed poll count fails the wall-clock case, a wait ignoring a dead producer fails the exit case, a wait that refuses to poll fails the completion case, removing the teardown guard rebuilds the home, and applying that guard too broadly loses a terminal ledger line for a retirement whose ledger lives outside the home. A near-vacuous race case was deliberately deleted rather than kept, because no external control could force its interleaving. The full CI serial shard 2 lane now runs green locally (25 tests, 0 failed, the previously red test running for real with gate_skip=false); the cleanup suite passes with both new cases; lint, the coverage guard, and the documentation-audience check are clean.

Both named hypotheses are disconfirmed with evidence: treehouse is installed only in the Herdr CI job and never in the portable serial shard, and this suite never invokes it; and the serial lane is now four sharded jobs completing in about 9.5 minutes against a 15-minute cap, so it is not near its timeout.

What Changed

  • tests/lib.sh gains fm_test_wait_file, a wall-clock-bounded wait that also distinguishes a dead producer from an expired bound; the six blocked-worker waits in tests/fm-remote-secondmate-lifecycle-e2e.test.sh now use it (90s for remote transactions, 30s for local markers) instead of a 250-iteration poll count, fixing the "Behavior portable serial 2" check that was red on the fork trunk base itself — the pipeline's Test lane confirms the formerly failing suite now runs green for real (22 ok, gate_skip=false).
  • bin/fm-teardown.sh now decides wake-ledger containment before removing a secondmate home and skips the terminal ledger write when the ledger lived inside the home it just removed (matched by path containment or the vanished directory), so a remote secondmate retirement no longer rebuilds the deleted home as a stray data/.parent-route/wake-ledger.tsv tree; covered by new retirement-guard cases in tests/fm-teardown.test.sh, with an SC1007 lint cleanup in the new path helper.
  • The branch also carries the fork trunk's accumulated history not yet on origin/main — 46 commits spanning the fleet launcher menu, model registry and zero-budget spawn gate, wake-outcome ledger, fleet admission control, remote secondmate provisioning/doctor/job workers, LoopSpec, and the fork/upstream reconciliations — which is why the full delta spans ~204 files.

Risk Assessment

✅ Low: The review-fix commit implements every element of the round-1 fix instructions exactly (pre-removal containment verdict, vanished-dir arm removed, write position and warning path preserved, all three test directions kept/added), and the new path-resolution helper verified correct across edge cases by static analysis and an isolated probe, leaving only one minor idiom-hardening nit.

Testing

Ran the three targeted suites through the project's own runner — the previously red lifecycle e2e (now fully green with gate_skip=false), the teardown suite with its three new retirement-guard cases, and the new wait-helper contract test — plus a manual red/green control proving the base teardown rebuilds the retired home (stray wake-ledger.tsv tree) while the fixed teardown does not; all green, worktree left clean. No visual evidence applies: the change is shell-script and test behavior with no rendered surface, so CLI transcripts are the end-user-visible artifact.

Evidence: Previously red lifecycle e2e suite passing (22 ok, exit 0, gate_skip=false)
FM_TEST_BEGIN 2026-08-09T00:55:48Z tests/fm-remote-secondmate-lifecycle-e2e.test.sh family=secondmate expected_gate_skip=none
ok - overlapping remote home provisioning serializes through publication and rollback
ok - remote seed rollback preserves serialized competing routes
ok - unknown readiness preserves its route and brief for reconciliation
ok - remote seeding checks, repairs, and re-checks readiness, then stops on a remaining gap
ok - remote seeding proceeds once the repair closes every gap
ok - remote seed registers the route and provisions the whole home and project clone on that host
ok - remote inheritance rejects incomplete and superseded payload generations
ok - mixed local and remote routes validate without migration
ok - remote spawn launches on the remote-local backend and records a host-qualified route
ok - legacy and mismatched remote endpoints fail closed before backend access
ok - non-herdr remote endpoints are refused without changing either route
ok - remote spawn serializes inheritance through launch publication
ok - marked send and routed reply complete through the existing parent correlation owner
PR_CHECK_MIGRATION: watcher ownership is ambiguous; review state/.watch.lock before rearming polls
ok - partial remote inheritance retains reread intent through bootstrap convergence
ok - config push and bootstrap serialize remote inheritance convergence
WARNING: queued wakes pending - drain them with bin/fm-wake-drain.sh before anything else.
ok - remote inherited config retains and retries a failed live reread nudge
ok - fleet snapshot projects mixed local and remote structured state
ok - remote update imports and fast-forwards the persistent home on its configured host
PR_CHECK_MIGRATION: watcher ownership is ambiguous; review state/.watch.lock before rearming polls
ok - startup repairs remote readiness before probing without relaunching
PR_CHECK_MIGRATION: watcher ownership is ambiguous; review state/.watch.lock before rearming polls
ok - startup reports alive legacy backends without changing their routes
ok - unreachable remote state remains unknown with no local respawn or failover
ok - remote retirement refuses child work, then removes only its own endpoint while a shared-session sibling survives
ALL TESTS PASSED
FM_TEST_END 2026-08-09T00:59:00Z tests/fm-remote-secondmate-lifecycle-e2e.test.sh exit=0 duration_ms=192732 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=192768
FM_TEST_SUMMARY_FAMILY family=secondmate count=1 duration_ms=192732 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-remote-secondmate-lifecycle-e2e.test.sh duration_ms=192732
Evidence: Red/green control: base ed376cf teardown rebuilds the removed home (wake-ledger.tsv stray tree listed, exit 1); fixed teardown green in the same environment

not ok - secondmate-home-owned-data: retirement rebuilt the removed home .../secondmate-home/data/.parent-route/wake-ledger.tsv suite exit: 1 === GREEN: fixed (f1ac922) bin/fm-teardown.sh + same tests, same environment === ok - secondmate retirement writes nothing back into the home it removed ok - secondmate retirement still records a terminal line in a ledger outside that home suite exit: 0

=== RED CONTROL: base (ed376cf) bin/fm-teardown.sh + new retirement tests ===
not ok - secondmate-home-owned-data: retirement rebuilt the removed home
/tmp/fm-teardown-tests.okgQhv/secondmate-home-owned-data/secondmate-home
/tmp/fm-teardown-tests.okgQhv/secondmate-home-owned-data/secondmate-home/data
/tmp/fm-teardown-tests.okgQhv/secondmate-home-owned-data/secondmate-home/data/.parent-route
/tmp/fm-teardown-tests.okgQhv/secondmate-home-owned-data/secondmate-home/data/.parent-route/wake-ledger.tsv
suite exit: 1
=== GREEN: fixed (f1ac922) bin/fm-teardown.sh + same tests, same environment ===
ok - secondmate retirement writes nothing back into the home it removed
ok - secondmate retirement still records a terminal line in a ledger outside that home
suite exit: 0
Evidence: Full teardown suite green including the three new retirement-guard cases
FM_TEST_BEGIN 2026-08-09T00:56:01Z tests/fm-teardown.test.sh family=pr-forge expected_gate_skip=none
ok - local-only worktree with HEAD on a fork remote is torn down (fix holds)
ok - teardown prompts tasks-axi backlog refresh when compatible
ok - teardown honors config/backlog-backend=manual even when tasks-axi is compatible
ok - local-only worktree with truly unpushed work is refused (safety preserved)
ok - local-only worktree with work merged into local main is torn down (no regression)
ok - no-mistakes worktree with HEAD on origin is torn down (no regression)
ok - no-mistakes worktree with genuinely unlanded work is refused (safety preserved)
ok - local-only worktree with unpushed work is torn down under --force (escape hatch)
ok - teardown completes when an exact busy-state sidecar is already absent
ok - herdr teardown removes pane-owned escalation dedupe state
ok - herdr flat teardown refuses before returning the isolated copy under lock contention and the retry completes cleanly
ok - herdr flat teardown never erases records when pane presence is unparseable
ok - herdr flat teardown preflight refuses before every destructive change
ok - forced secondmate teardown preflights every Herdr child before cleanup mutation
ok - forced secondmate teardown retains Herdr child identity until exact pane disappearance
ok - forced teardown retains a nested secondmate home and its grandchild's Herdr identity when the grandchild close is unconfirmed
ok - secondmate retirement writes nothing back into the home it removed
ok - secondmate retirement still records a terminal line in a ledger outside that home
ok - secondmate retirement creates a missing external ledger directory and records its terminal line
ok - herdr projection teardown retires its journal only after confirming the exact recorded pane is gone
ok - herdr projection teardown retains every record when post-close presence is unknown
ok - squash-merged + deleted-branch worktree (PR merged) is torn down (the fix)
ok - releasing a task whose PR has not landed leaves a durable landing record
ok - releasing a task whose PR already merged leaves no landing record
ok - squash-merged PR accepts a local HEAD that is an ancestor of the final PR head
ok - teardown discovers a merged PR by branch name and tears down when no pr= was ever recorded
ok - squash-merged PR accepts replayed unpushed local patches contained in the PR head
ok - merged PR does not allow teardown after a later local commit
ok - fm-pr-check does not refresh PR head after HEAD moves
ok - fm-pr-check records the remote PR head when the local worktree lags
ok - worktree whose content already landed in the default branch is torn down (content fallback)
ok - content fallback refreshes origin default before comparing trees
ok - work squash-merged into the fork trunk is torn down though no tracking ref follows it
ok - genuinely unlanded work is still refused on a fetch/push split
ok - an unreadable landing target refuses rather than falling back to the upstream answer
ok - content landed on the upstream trunk is still accepted when the fork trunk is readable
ok - an ordinary single-remote repository names no landing ref and is unaffected
ok - dirty worktree is refused even when its committed work has landed (dirty always wins)
ok - spawn-written turn-end artifacts (claude, opencode, grok, kimi) do not read as uncommitted work
ok - untracked work beside a turn-end artifact still refuses (allowlist is per-path, not per-directory)
ok - gh lookup error with content not in default refuses (fail-safe)
ok - provably-stale worktree index.lock (old, no live holder) is cleared and teardown succeeds
ok - live-held worktree index.lock is never removed and teardown refuses
ok - lsof errors leave worktree index.lock in place and refuse teardown
ok - stale lock cleanup rechecks and refuses dirty worktree before return
ok - normal repo index.lock is resolved from the worktree and cleared when stale
ok - lock mtime read failures leave worktree index.lock in place and refuse teardown
ok - transient index.lock cleared after first failed return is retried successfully without force-remove
ok - persistent index.lock exhausts retries and refuses without force-removing the lock
ok - empty retry wait overrides use the default without aborting teardown
ok - fractional legacy retry wait remains supported without arithmetic
ok - teardown records the terminal ledger line with the meta's profile before deleting it
ok - a --force teardown records the task as abandoned rather than landed
ok - an unwritable ledger warns but never fails or halts a teardown
ok - a task's own parked no-mistakes run is aborted, not orphaned, before the worker is removed
ok - teardown refuses before reap or removal when a task-owned run remains parked
ok - a different run cannot confirm the targeted abort
ok - empty post-abort status is not accepted as confirmation
ok - the CLI's exact run-not-found signal confirms completion
ok - a parked run on another branch is never aborted by this task's teardown (ownership is precise)
ok - a task-owned autonomous running step is left alone rather than aborted
ok - a leaked descendant process rooted under the task's worktree is reaped by teardown, not left surviving
ok - a leaked descendant process rooted under the task's per-task tasktmp is reaped by teardown too
ok - missing lsof falls back to reaping the tmux pane process group
ok - an erroring lsof scan refuses teardown and preserves the task
ok - a reused pid with a different start time is never force-killed
ok - an exec change preserves birth identity and the process is reaped
ok - a process spawned during grace is reaped on a later pass
ok - persistent leaked processes refuse teardown after bounded retries
ok - a process exiting during identity lookup does not block teardown
ok - the run abort and the leaked-process reap both complete before the destructive worktree return
FM_TEST_END 2026-08-09T00:57:18Z tests/fm-teardown.test.sh exit=0 duration_ms=77577 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=77613
FM_TEST_SUMMARY_FAMILY family=pr-forge count=1 duration_ms=77577 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-teardown.test.sh duration_ms=77577
Evidence: fm_test_wait_file contract test: wall-clock bound, dead-producer verdict, immediate return on marker
FM_TEST_BEGIN 2026-08-09T00:55:49Z tests/fm-test-lib-wait.test.sh family=unclassified expected_gate_skip=none
ok - the wait returns once its marker appears
ok - the bound is wall-clock seconds, spent in full and scaled to the caller's request
ok - a producer that dies without its marker is reported apart from a timeout
ALL TESTS PASSED
FM_TEST_END 2026-08-09T00:55:56Z tests/fm-test-lib-wait.test.sh exit=0 duration_ms=6414 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=6450
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=6414 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-test-lib-wait.test.sh duration_ms=6414

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Push main to origin, or rebase your branch onto origin/main, before gating.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-teardown.sh:2375 - The vanished-directory arm of the new ledger guard (|| [ ! -d "$(dirname "$LEDGER_PATH")" ]) skips the terminal wake-ledger line for ANY secondmate teardown whose ledger parent directory does not exist, not just for the removed home. Concrete regression: with FM_WAKE_LEDGER redirected outside the home to a not-yet-created directory, fm-wake-ledger.sh previously created the directory and recorded the line (and a failed write at least emitted the 'wake ledger terminal line not recorded' warning); now the write is skipped entirely and silently, since the skip path bypasses the warning too. The shipped counter-test (test_secondmate_retirement_still_records_a_ledger_outside_the_home) only covers an external ledger whose directory already exists. The two-armed guard shape is documented as a deliberate choice, so this challenges intent rather than being auto-fixable; a minimal mitigation is to emit the existing warning when the vanished-dir arm fires without path containment matching.
  • ℹ️ tests/fm-remote-backlog-handoff.test.sh:234 - The same defect class fixed by this branch — blocked-worker waits bounded by a poll count (250 iterations of sleep 0.02) instead of wall clock — survives in tests/fm-remote-backlog-handoff.test.sh at lines 139, 234, and 309, waiting on the same SSH-boundary remote transactions (fm-backlog-handoff.sh behind FM_FAKE_SSH_MODE barriers). These sites are one loaded runner away from the identical 'never reached its blocked write' flake. Leaving them unconverted is consistent with the intent's required minimality, so no action for this change; they are a natural follow-up now that tests/lib.sh owns fm_test_wait_file.

🔧 Fix: decide ledger containment before removing the secondmate home
1 info still open:

  • ℹ️ bin/fm-teardown.sh:889 - nearest_existing_abs_path resolves with a bare cd "$dir" while the same file's other containment/canonicalization helpers (lines 211, 239, 424-431) deliberately use CDPATH='' cd --. If an operator exports CDPATH and a relative path component reaches the helper, cd can resolve through a CDPATH entry and also prints the destination into the command substitution, yielding a corrupted resolved path and therefore a wrong ledger-containment verdict. Exposure is minimal because HOME_PATH and LEDGER_PATH are effectively always absolute in this codebase; changing the line to resolved=$(CDPATH='' cd -- "$dir" 2>/dev/null && pwd -P) matches the file's established defensive idiom.
✅ **Test** - passed

✅ No issues found.

  • bin/fm-test-run.sh tests/fm-remote-secondmate-lifecycle-e2e.test.sh — the suite behind the red CI check; 22 ok, exit 0, gate_skip=false, including the formerly failing "first inheritance transaction" barrier and the final retirement case
  • bin/fm-test-run.sh tests/fm-teardown.test.sh — full cleanup suite green with all three new retirement-guard cases (nothing written into the removed home; external ledger still recorded; missing external ledger directory still created)
  • bin/fm-test-run.sh tests/fm-test-lib-wait.test.sh — fm_test_wait_file contract: wall-clock bound scaled to the request, dead producer reported apart from timeout, immediate return on marker
  • Manual red/green control in an ephemeral /tmp tree: base ed376cf bin/fm-teardown.sh under the new retirement tests fails with the home rebuilt as a stray tree containing data/.parent-route/wake-ledger.tsv (exit 1); restoring the f1ac922 teardown in the same environment turns both cases green (exit 0)
  • bin/fm-test-run.sh --list --lane portable-serial-2of4 — confirmed the lifecycle e2e suite is selected by the exact CI serial shard that was red
⚠️ **Document** - 2 issues (1 error, 1 info)
  • 🚨 bin/fm-teardown.sh:880 - bin/fm-lint.sh fails at the target commit: shellcheck SC1007 at bin/fm-teardown.sh:880 (local path=$1 dir=$1 suffix= resolved in the new nearest_existing_abs_path helper; shellcheck wants suffix=''). The failure is pre-existing in this change's own code (verified identical with my documentation edits stashed), CI's lint job runs bin/fm-lint.sh directly, so the lint lane is red at f1ac922 - contradicting the intent's claim that lint was clean and the goal of a green fork trunk. The fix is a one-character code edit, which this documentation-only pass is forbidden to make.
  • ℹ️ docs/fm-test-portable-shards.md:67 - Follow-up: docs/fm-test-portable-shards.md's dated shard-balance evidence (2026-08-02: 18 scripts in portable-serial-2of4, ~5-minute shards with ~3x timeout margin) has drifted - the intent measured 25 scripts in shard 2 and ~9.5 minutes against the 15-minute cap, and this change adds tests/fm-test-lib-wait.test.sh to the serial remainder. The doc's own refresh procedure needs per-shard timing artifacts from a green CI run, which can only exist after this change lands, so refresh the hints table then rather than hand-editing the dated evidence now.
🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: quiet SC1007 with explicit empty suffix initializer
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

The remote secondmate lifecycle suite failed on the fork trunk itself, so every
open fork PR inherited a red required check. The config-push inheritance barrier
waited 250 poll iterations for its deliberately blocked write; on the CI runner
those iterations elapse in 5.3 seconds, while the transaction needs longer than
that to traverse its SSH-boundary jobs - the sibling spawn barrier in the same
job took about 16 seconds. Upstream raised exactly this bound to 1500 in kunchenguid#1727
on 2026-08-04; the fork reconciliation a day later landed 250 at that one site
while keeping 1500 at its sibling.

A poll count is not a duration. It shrinks precisely when the work it waits for
is slowest, so restoring a larger count would leave the same defect one loaded
runner away. tests/lib.sh now owns fm_test_wait_file, which bounds the wait by
wall clock the way bin/fm-remote-job-lib.sh already bounds its own polls, and
distinguishes a producer that died from a bound that expired. Every blocked
worker wait in the suite uses it: 90 seconds for a remote transaction, 30 for a
local marker, both hang tripwires with margin over a measured 26-second worst
case rather than expected durations - a healthy wait ends when its marker
appears and costs nothing extra.

Measured on the base: the transaction reaches its blocked write after 297-319
iterations (23-26 seconds) on a loaded runner, and every serialization assertion
after it passes, which is the disconfirming evidence against a code-side defect.

tests/fm-test-lib-wait.test.sh pins the helper's contract. Each of its three
guarantees was witnessed red under a matching defect: a fixed poll count fails
the wall-clock case, a wait that ignores a dead producer fails the exit case,
and a wait that refuses to poll fails the completion case.
…loaded runner

Behavior portable serial 3 failed on "Pi must deliver the actionable wake
after bounded hung-successor recovery": the 250ms FM_PI_ARM_READY_TIMEOUT_MS
window doubles as the time a spawned successor has to reach its arm= append
before retirement SIGTERMs it. On a loaded runner bash -l startup exceeds
250ms, the successor dies without logging its row, and the recovery counts
misread (reproduced 6/6 on a starved core with the exact CI signature:
"expected one successor plus two retries, got 1/2"; the same load passes
6/6 at 2000ms, interleaved).

Raise the window to 2000ms at all six sites that shrank it (Pi and OpenCode
hung/unretired/late twins share the race), and bound the driver waits the
new window outgrows by 30s of wall clock instead of an iteration count,
per the wall-clock wait guideline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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