fix(bin): keep declared pauses off wedge cadence - #2738
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c35a9345f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if status_pause_damps_wedge "$last" \ | ||
| || { ! afk_present && status_is_paused_or_captain_held "$last"; }; then | ||
| handle_paused_stale "$win" "$task" "$h" |
There was a problem hiding this comment.
Reconcile the run before damping away-mode wedges
Captain, when AFK is active and a task starts an authoritative run without appending a new status event, an older paused: line still makes this new first clause call handle_paused_stale before any fm-crew-state.sh read. That clears the short wedge timer and leaves a hung busy run on repeated hour-long pause rechecks, so the old pause can mask exactly the busy-run wedge this bound detects; gate the pause on current-state reconciliation so run-step working retains precedence.
AGENTS.md reference: AGENTS.md:L133-L133
Useful? React with 👍 / 👎.
2c35a93 to
1427342
Compare
1427342 to
bdf8b07
Compare
Intent
Publish the already-implemented declared-pause wedge correction as one upstream PR for kunchenguid/firstmate issue #2614 now that publication is authorized and the maintainer marked it ready for PR. On current origin/main, a current declared paused: external wait must use FM_PAUSE_RESURFACE_SECS and must never produce repeated idle/busy possible-wedge or demand-deep-inspection escalation in ordinary or away-mode supervision; a newer non-pause status must supersede the wait and restore normal bounded wedge detection so an old pause cannot hide a real wedge forever, while authoritative active-run precedence remains intact. Keep this policy in the existing shared classifier owner, preserve non-paused max-defer and wedge reliability, and do not broaden the change into speculative busy-pane damping. Preserve or improve executable regressions for paused suppression, pause supersession, ordinary escalation, away-mode watcher routing, and daemon rechecks; run focused classifier, watcher, daemon, and AFK suites, documentation audience checks, exact bin/fm-lint.sh, and the complete no-mistakes pipeline. Port only the intended correction from preserved commit d6f4659 onto current origin/main without altering the preserved source branch or old copy, and include no private data/state/config, credentials, local paths, project content, agent co-author, or unrelated changes. The external-healthy-watcher test was treated as branch-relevant, reproduced identically on this branch and clean origin/main, and proven to fail only under Node 22 TS import support while both pass unchanged under available Linux Node 24, so do not weaken or alter that unrelated assertion. Push only fm/firstmate-declared-pause-wedge-upstream-pr through the captain-authorized Omar-Nawaf/firstmate fork and open one PR against kunchenguid/firstmate:main referencing #2614; never merge.
What Changed
status_pause_damps_wedgepredicate to the sharedbin/fm-classify-lib.shand routed both supervision modes through it: a currentpaused:external wait now takes the boundedFM_PAUSE_RESURFACE_SECSrecheck cadence — whether its pane is idle or busy in the wait's own poll loop — instead of repeated possible-wedge / demand-deep-inspection escalation, and a wedge-decorated watcher reason no longer overrides that pause when the daemon classifies the wake.tests/fm-daemon.test.shandtests/fm-watch-triage.test.shto cover busy declared waits, pause supersession, ordinary escalation, away-mode routing, and daemon rechecks, and updateddocs/architecture.md,docs/configuration.md, and the AFK skill doc to describe the latest-status-line pause policy.Risk Assessment
✅ Low: The change is a well-bounded, internally consistent port of the authorized declared-pause correction: both supervision modes route through the one shared classifier predicate, latest-status supersession and active-run precedence are verifiably preserved at every changed decision point, docs match the new behavior, and the rewritten tests are behavioral regressions that would fail under the old code.
Testing
Ran the focused daemon, watcher-triage, classifier, and documentation-audience suites at the target commit (all pass, including every preserved regression named in the intent), proved all four new/changed pause regression tests fail on the base commit with the exact pre-fix symptoms, and captured a manual before/after transcript of the real watcher and daemon showing pause suppression, the bounded awaiting-external re-surface, and wedge restoration on supersession; no failures or intent violations found.
Evidence: Manual before/after demo: base watcher wedge-escalates a declared busy wait; target watcher takes the pause cadence, daemon re-surfaces it bounded, newer status restores wedge detection
Source: Manual before/after demo: base watcher wedge-escalates a declared busy wait; target watcher takes the pause cadence, daemon re-surfaces it bounded, newer status restores wedge detection
=== Run 1: BASE watcher (a0cec26, pre-fix) on the declared wait === watcher surfaced a wake and exited; the wake reason handed to supervision: stale: demo:fm-wait (idle 501s, possible wedge, escalation 3, demand-deep-inspection: same pane has wedge-escalated 3 times in a row - do not re-absorb on the run-step/pane state alone) === Run 2: TARGET watcher (2c35a93, fixed) on the identical fixture === watcher completed a full poll cycle without surfacing any wake. pause-cadence marker .paused-demo_fm-wait installed (FM_PAUSE_RESURFACE_SECS recheck owns it now). wedge timer .stale-since-demo_fm-wait cleared. deep-inspection escalation counter .wedge-escalations-demo_fm-wait cleared. === Run 3: TARGET daemon housekeeping past FM_PAUSE_RESURFACE_SECS (busy pane) === escalation digest the captain receives (state/.subsuper-escalations): paused 5000s (awaiting external, recheck whether the wait still holds): sess:fm-held-w12 pause marker reset, so the bounded recheck window repeats. === Run 4: TARGET watcher after a NEWER non-pause status supersedes the wait === watcher surfaced a wake and exited; ordinary bounded wedge detection is restored: stale: demo:fm-wait (idle 502s, possible wedge, escalation 3, demand-deep-inspection: ...)Evidence: New watcher regression tests fail on base a0cec26 (pre-fix symptoms reproduced)
Source: New watcher regression tests fail on base a0cec26 (pre-fix symptoms reproduced)
=== watch-afk-busy-pause (target-commit test against BASE code a0cec26) === not ok - away-mode busy declared pause wedge-escalated: stale: test:fm-afk-wait (idle 501s, possible wedge, escalation 3, demand-deep-inspection: same pane has wedge-escalated 3 times in a row - do not re-absorb on the run-step/pane state alone) exit=1 === watch-live-pause-bounded (target-commit test against BASE code a0cec26) === not ok - a live declared pause surfaced immediately: stale: test:fm-gate exit=1Evidence: New daemon regression tests fail on base a0cec26 (pre-fix symptoms reproduced)
Source: New daemon regression tests fail on base a0cec26 (pre-fix symptoms reproduced)
=== daemon-wedge-decoration (target-commit test against BASE code a0cec26) === not ok - an enriched wedge overrode a declared external wait: sess:fm-suffix-paused (idle 500s, possible wedge, escalation 3, demand-deep-inspection: ...) exit=1 === daemon-busy-pause-recheck (target-commit test against BASE code a0cec26) === not ok - a busy declared wait was not re-surfaced as an awaiting-external recheck exit=1Evidence: fm-watch-triage suite at target (51 ok)
Source: fm-watch-triage suite at target (51 ok)
Evidence: fm-daemon suite at target (99 ok)
Source: fm-daemon suite at target (99 ok)
Evidence: fm-classify-decision-key suite at target (13 ok)
Source: fm-classify-decision-key suite at target (13 ok)
Evidence: fm-documentation-audiences suite at target (4 ok)
Source: fm-documentation-audiences suite at target (4 ok)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-supervise-daemon.sh:1046- Daemon housekeeping (2b) no longer retires a pause marker when the pane is busy, so a crew that genuinely resumed work but never appended a newer non-pause status line will keep receiving the bounded 'awaiting external' recheck digest once per FM_PAUSE_RESURFACE_SECS until a newer status supersedes the pause. This is the documented, intended trade-off (busyness cannot distinguish a wait's poll loop from resumed work), and the recheck explicitly asks firstmate to confirm the wait still holds - noting it here as the one place future maintainers might expect busy-implies-resumed.✅ **Test** - passed
✅ No issues found.
bash tests/fm-daemon.test.shat target 2c35a93 (99 ok, includes busy-pause recheck, wedge-decoration-never-overrides-pause, ordinary escalation, max-defer, afk presence-gating)bash tests/fm-watch-triage.test.shat target 2c35a93 (51 ok, includes away-mode busy declared pause, liveness-independent pause cadence, pause supersession restoring wedge escalation, active-run precedence recheck, secondmate exemptions)bash tests/fm-classify-decision-key.test.shat target (13 ok, shared classifier suite)bash tests/fm-documentation-audiences.test.shat target (4 ok, covers the docs/SKILL.md edits)Regression proof: ran target-committest_stale_diagnostic_wedge_survives_busy_housekeepingandtest_housekeeping_paused_busy_pane_still_rechecksagainst base a0cec26 code — both fail pre-fix (declared wait escalated as possible wedge / busy wait silently dropped)Regression proof: ran target-committest_afk_busy_declared_pause_uses_pause_cadenceandtest_declared_pause_and_exited_captain_hold_are_boundedagainst base a0cec26 code — both fail pre-fix (away-mode wedge escalation with demand-deep-inspection / live pause surfaced immediately)Manual end-to-end demo driving realbin/fm-watch.shand daemonhousekeepingon identical fixtures: base watcher emitspossible wedge, escalation 3, demand-deep-inspectionfor a declared busy wait; target watcher absorbs it into the pause cadence and clears wedge state; daemon re-surfaces it past FM_PAUSE_RESURFACE_SECS aspaused 5000s (awaiting external, recheck whether the wait still holds)and resets the window; a newerworking:status restores the wedge escalationHygiene: verifiedstatus_pause_damps_wedgeis defined once in bin/fm-classify-lib.sh and called by both supervision modes; commit author is Omar Nawaf with no Co-Authored-By, and the diff contains no credentials, local paths, or private data✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.