fix(bin): sync upstream poll-derived watcher beacon grace - #52
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.shaddsfm_poll_derived_grace(), the single owner of the guard-grace derivationmax(300, poll + 60), defaulting to$FM_POLLwhen no argument is given.bin/fm-watch.sh,bin/fm-claude-stop-autoarm.sh, andbin/fm-turnend-guard.shnow derive their staleness grace fromfm_poll_derived_graceinstead of a flat 300s constant: the watcher'sWATCHER_STALE_GRACEline is replaced by a comment plus the derived default, the auto-arm hook exports its resolvedGRACEtofm-watch-arm.sh, and the turnend guard's away-mode beacon check now uses a dedicated poll-derivedAFK_GRACEbeacon-age test instead of the oldFM_SUP_WATCHER_FRESHboolean.docs/turnend-guard.mdgains a "Guard grace and the poll cadence" section (with a matching one-line update indocs/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 newfm_test_base_path_sanshelper intests/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)
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=600bash tests/fm-claude-stop-autoarm.test.sh — 41/41 ok, including the new test_long_poll_grace_reaches_arm_wrapperbash tests/fm-remote-job-orphan-reap.test.sh — 7/7 okgit diff --stat 1c741b9f 28bdfc8a — confirmed 13 files changed, +268/-32, matching the intent's file list exactlyManual environment fix: created a throwaway PATH shim (outside the worktree, deleted after use) translating GNUtouch -d "@EPOCH"to BSDtouch -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.