Skip to content

fix(bin): sync upstream poll-derived watcher beacon grace - #52

Merged
zeeshaanahmad merged 2 commits into
mainfrom
fm/upstream-batch-18-to-tip
Sep 8, 2026
Merged

zeeshaanahmad merged 2 commits into
mainfrom
fm/upstream-batch-18-to-tip

Conversation

@zeeshaanahmad

Copy link
Copy Markdown
Owner

Intent

Captain's standing policy for this fork (2026-09-05, verbatim): "the main goal for firstmate is to become sync with upstream. once we reach that state, we avoid introducing more changes and stay aligned with the upstream. until then you can keep adding work that helps aligning with the upstream easier and reaches the intended state quickly."

Captain's conflict rule (2026-09-05, verbatim): "if there is something upstream and our code both worked on and fixed but approaches differ, take the upstream change, however, if our change is better than upstream, file a PR. for other items, take the upstream changes."

Captain's decision on 2026-09-07 (verbatim): "park whatever diverges from upstream. our goal was to sync with upstream for firstmate. if thats achieved and new updates can easily be pulled from upstream, thats it. no further work on firstmate." Sync batches are the only firstmate work; no fork-side improvements, no new upstream PR candidates unless a measured defect forces one.

The ask this task serves: upstream sync batch 18 - bring the fork's origin/main (now 1c741b9: batch 17 landed as #51, waypoint 72bfdd0) up to upstream's current tip, waypoint 891dc51 ("fix(bin): derive watcher beacon staleness grace from poll cadence (kunchenguid#3946)"). One upstream commit, 13 files, +268/-32: bin/fm-wake-lib.sh gains fm_poll_derived_grace (max(300, poll+60), the single owner of the guard-grace derivation); bin/fm-turnend-guard.sh, bin/fm-claude-stop-autoarm.sh, and bin/fm-watch.sh derive their grace from it (the watcher's WATCHER_STALE_GRACE line is replaced by a comment plus the derived default; the turnend guard's away-mode beacon test uses a separate AFK_GRACE); docs/turnend-guard.md gains a "Guard grace and the poll cadence" section; docs/configuration.md one line; tests: fm-turnend-guard (+93), tests/lib.sh (+40), fm-remote-job-orphan-reap (+28), fm-claude-stop-autoarm (+22), fm-tool-update-check, fm-bootstrap, fm-session-start.

What Changed

  • bin/fm-wake-lib.sh adds fm_poll_derived_grace(), the single owner of the guard-grace derivation max(300, poll + 60), defaulting to $FM_POLL when no argument is given.
  • bin/fm-watch.sh, bin/fm-claude-stop-autoarm.sh, and bin/fm-turnend-guard.sh now derive their staleness grace from fm_poll_derived_grace instead of a flat 300s constant: the watcher's WATCHER_STALE_GRACE line is replaced by a comment plus the derived default, the auto-arm hook exports its resolved GRACE to fm-watch-arm.sh, and the turnend guard's away-mode beacon check now uses a dedicated poll-derived AFK_GRACE beacon-age test instead of the old FM_SUP_WATCHER_FRESH boolean.
  • docs/turnend-guard.md gains a "Guard grace and the poll cadence" section (with a matching one-line update in docs/configuration.md), and test suites (fm-turnend-guard, fm-claude-stop-autoarm, fm-remote-job-orphan-reap, fm-tool-update-check, fm-bootstrap, fm-session-start, plus a new fm_test_base_path_sans helper in tests/lib.sh) add and update coverage for the new derivation and test infrastructure.

Risk Assessment

✅ Low: This is a faithful, verified sync of a single upstream commit: a line-level comparison confirms the fork's merge diff for all 13 files is an exact multiset match of upstream's own base-to-tip diff (no lines lost or altered), the two documented adjacency conflicts are correctly resolved (fork-only WATCHER_CLEANUP_LOCK_TICKS block and fork-added doc sentences both preserved verbatim), the new fm_poll_derived_grace arithmetic (max(300, poll+60) with safe non-numeric fallback to 15) is correct, call sites consistently thread the derived/overridable grace through (including exporting FM_GUARD_GRACE from the autoarm hook to fm-watch-arm.sh), and new tests exercise the poll-derived-grace widening, its bound against a dead/stale daemon, and its inapplicability outside away mode.

Testing

Ran the three test suites that directly exercise the new poll-derived-grace behavior (fm-turnend-guard, fm-claude-stop-autoarm, fm-remote-job-orphan-reap) — 139 tests total, all passing, including every new test added by this batch — after working around a pre-existing macOS/GNU-coreutils touch-syntax gap in the environment with a local, source-untouched PATH shim; the mechanical-only test-infra changes in fm-bootstrap/fm-session-start/fm-tool-update-check (a PATH-stripping helper and a variable rename, unrelated to the behavioral change) were kicked off but fm-bootstrap.test.sh's large subprocess-heavy suite was still running at report time and its result isn't included.

Evidence: Behavioral test output: poll-derived guard-grace hook tests (turnend-guard, claude-stop-autoarm, remote-job-orphan-reap)

Source: Behavioral test output: poll-derived guard-grace hook tests (turnend-guard, claude-stop-autoarm, remote-job-orphan-reap)

=== bin/fm-turnend-guard.sh behavior tests (tests/fm-turnend-guard.test.sh) ===
Includes the 4 new poll-derived-grace tests exercising the actual hook exit codes/output.
ok - fm-turnend-guard --claude: a dead lock owner reports the reclaim mechanism, not a live-owner stand-down
ok - fm-turnend-guard --claude: positive watcher recovery clears the recorded non-participation
ok - fm-turnend-guard --claude: a real auto-arm that never participates still reaches the bounded fail-open
ok - fm-turnend-guard --claude: verified fail-open is loud, bounded, attended, and non-repeating
ok - fm-turnend-guard --claude: a partial auto-arm failure record is never read as exhausted retries
ok - fm-turnend-guard --claude: away ownership excludes the Stop-autoarm fail-open
ok - fm-turnend-guard --claude: positive watcher recovery resets failure episode state
ok - fm-turnend-guard --claude: bounded claim wait avoids a token-consuming forced continuation
ok - fm-turnend-guard --claude: secondmate home re-blocks unclaimed and allows auto-arm-claimed stops
ok - fm-turnend-guard: a live away-mode daemon satisfies supervision with no watcher holding the lock
ok - fm-turnend-guard: away-mode daemon ownership survives a leftover dead watcher lock
ok - fm-turnend-guard: away mode with no daemon and no watcher still blocks
ok - fm-turnend-guard: away mode blocks on a dead away-mode daemon
ok - fm-turnend-guard: away mode blocks on a pid-reused away-mode daemon lock
ok - fm-turnend-guard: away-mode daemon ownership never substitutes for a fresh beacon
ok - fm-turnend-guard: a daemon lock proves nothing while away mode is off
ok - fm-turnend-guard: away-mode beacon freshness uses the poll-derived grace, not the flat default
ok - fm-turnend-guard: a dead away-mode daemon still blocks under the poll-derived grace
ok - fm-turnend-guard: the poll-derived grace is bounded, not unlimited
ok - fm-turnend-guard: with away mode off, the poll-derived grace never applies

=== bin/fm-claude-stop-autoarm.sh behavior tests (tests/fm-claude-stop-autoarm.test.sh) ===
ok - auto-arm: need vanishing mid-cycle closes without a rewake
ok - auto-arm: mid-cycle AFK hands triage to the daemon with no rewake
ok - auto-arm: active in a marked secondmate home
ok - auto-arm: a long FM_POLL with FM_GUARD_GRACE unset reaches fm-watch-arm.sh with the derived grace
ok - fm-lock: shared session-lock lib preserves the status path

=== fm-remote-job-orphan-reap behavior tests ===
ok - a worker whose code root still exists is never reaped
ok - a worker stops its whole tree once its code root is pruned
ok - a dry run reports the abandoned worker and signals nothing
ok - the reaper stops an abandoned worker's whole tree
ok - the reaper is idempotent

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Step was skipped.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-turnend-guard.test.sh — 91/91 ok, including the 4 new tests (test_hook_away_daemon_allows_beacon_within_poll_derived_grace, test_hook_away_daemon_blocks_dead_daemon_despite_poll_derived_grace, test_hook_away_daemon_blocks_beacon_older_than_poll_derived_grace, test_hook_no_afk_ignores_poll_derived_grace) that assert the hook's real exit code (0 allow / 2 block) and output under FM_POLL=600
  • bash tests/fm-claude-stop-autoarm.test.sh — 41/41 ok, including the new test_long_poll_grace_reaches_arm_wrapper
  • bash tests/fm-remote-job-orphan-reap.test.sh — 7/7 ok
  • git diff --stat 1c741b9f 28bdfc8a — confirmed 13 files changed, +268/-32, matching the intent's file list exactly
  • Manual environment fix: created a throwaway PATH shim (outside the worktree, deleted after use) translating GNU touch -d "@EPOCH" to BSD touch -t, then reran the above suites to get a genuine pass signal instead of a touch-syntax false failure
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

NewAiCoder-bot and others added 2 commits September 7, 2026 19:34
…nchenguid#3946)

* fix(bin): derive the away-mode beacon grace from the poll cadence

fm-turnend-guard.sh's away-mode branch required the watcher beacon to be
fresh within the flat FM_GUARD_GRACE default (300s), but the daemon starts a
fresh one-shot watcher only after it finishes handling the previous wake, and
that handling can legitimately outrun a fixed 300s window under load (a slow
registered check, a busy supervisor pane) with the daemon perfectly healthy
throughout. That misread a live, correctly-cycling daemon as down and blocked
the turn.

Add fm_poll_derived_grace, the single owner of the max(300, FM_POLL + 60)
formula, and have the away-mode branch, fm-claude-stop-autoarm.sh, and
fm-watch.sh's own runtime beacon-staleness check all derive their default
grace from it instead of the flat default. A dead daemon pid or a beacon
older than that grace still blocks, so a genuinely lapsed away mode still
alarms; every other check is unchanged.

fm-claude-stop-autoarm.sh computed the derived grace into GRACE but its two
fm-watch-arm.sh invocations called the wrapper bare, so the wrapper fell back
to its own flat 300s default and could reject a healthy long-poll watcher.
Both invocations now pass FM_GUARD_GRACE="$GRACE" through explicitly, and a
new test proves a long FM_POLL with FM_GUARD_GRACE unset reaches
fm-watch-arm.sh with the derived value.

Also drops fm_last_activity_age, added alongside the derivation but never
called anywhere in the tree; fm-inactive-reconcile.sh already owns that
computation.

* no-mistakes(review): Remove dead WATCHER_STALE_GRACE assignment in fm-watch.sh

* no-mistakes(document): Update FM_WATCHER_STALE_GRACE default note for poll-derived grace

---------

Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
Brings the fork up to upstream waypoint
891dc51 ("fix(bin): derive watcher beacon
staleness grace from poll cadence (kunchenguid#3946)"), one upstream commit over 13
files, +268/-32.

bin/fm-wake-lib.sh gains fm_poll_derived_grace, the single owner of the
max(300, poll+60) derivation; bin/fm-watch.sh, bin/fm-turnend-guard.sh and
bin/fm-claude-stop-autoarm.sh derive their grace from it. The turnend guard's
away-mode beacon test gets its own AFK_GRACE so a daemon still cycling past a
flat 300s window is not misread as down; the flat $GRACE is unchanged for
every other check on that page.

Two conflicts, both adjacency, neither a competing fix:

1. bin/fm-watch.sh - upstream removed
   WATCHER_STALE_GRACE=${FM_WATCHER_STALE_GRACE:-${FM_GUARD_GRACE:-300}} from
   above the source-guard comment and re-added it below POLL, derived from
   fm_poll_derived_grace. The fork's PR 7 (2386360)
   WATCHER_CLEANUP_LOCK_TICKS block sits directly beneath the removed line and
   is fork-only. Resolved as upstream's removal plus the fork's cleanup-lock
   block unchanged; grep -c WATCHER_CLEANUP_LOCK_TICKS is 2, unchanged from
   the base.

2. docs/turnend-guard.md - upstream reworded the
   "tests/fm-turnend-guard.test.sh covers ..." sentence to name the away-mode
   beacon's poll-derived grace cases; the fork's PR 33 (deb6698) added two
   sentences after it. Resolved as upstream's sentence followed by the fork's
   two sentences unchanged. The resolved paragraph now reads:

     `tests/fm-turnend-guard.test.sh` covers the predicate, main and
     secondmate primary scope, child-worktree exclusion, `FM_HOME` and
     `FM_STATE_OVERRIDE` precedence, the live-lock and fresh-beacon guard
     predicate, the cooperative `--claude` open-generation claim wait,
     monotonic failed-epoch progression, bounded attended fail-open,
     post-alarm continuation suppression, positive recovery reset, generation
     and legacy claim cases that must block or clear instead of allowing a
     blind stop, away-mode daemon ownership between watcher cycles and over a
     watcher lock left behind by an exited watcher, plus its dead, pid-reused,
     absent, stale-beacon, and away-mode-off negatives, the away-mode beacon's
     poll-derived grace widening for a live daemon still mid-cycle and its
     bound against a dead daemon, a beacon older than that wider grace, and
     FM_POLL's inapplicability with away mode off, Pi logical-run latching,
     missing-`jq` behavior, all five primary registrations, Grok native and
     legacy selection, typed field precedence, malformed input, and
     exactly-one-path safety.
     It also covers the non-participation path end to end over the real
     auto-arm: a live foreign session lock makes it stand down leaving the
     state directory byte-for-byte unchanged, the guard still blocks exactly
     the bounded budget and then reaches its loud fail-open, a participating
     auto-arm never trips that path however long it blocks, a partial
     auto-arm failure record is never reported as exhausted retries, and
     positive watcher recovery clears the recorded episode.
     `FM_CLAUDE_LIVE_E2E=1 tests/fm-claude-stop-autoarm-live-e2e.test.sh` is
     the opt-in guard that proves the same non-participation path against the
     installed Claude, alongside the vendor fact it depends on - that a
     refused Stop still runs its `asyncRewake` sibling - and
     [`verification/supervision.md`](verification/supervision.md#claude-stop-hook-concurrency-2026-09-02)
     records the dated result.

Silent-splice sweep: every line upstream added across all 13 files is present
in the merged tree (0 missing), and each file's origin/main -> merged delta is
identical to upstream's own 72bfdd0 -> 891dc51 delta, so no upstream line
and no fork line was lost. Fork markers unchanged: PR 47's "the guard is
progress, not depth" comment in bin/fm-wake-lib.sh is still present, and
.github/workflows/ is byte-identical to upstream.
@zeeshaanahmad
zeeshaanahmad merged commit 232cc7c into main Sep 8, 2026
14 checks passed
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.

2 participants