Skip to content

feat(watch): add staleness auto-close for idle ship tasks - #27

Merged
trillium merged 9 commits into
mainfrom
fm/staleness-autoclose
Aug 1, 2026
Merged

feat(watch): add staleness auto-close for idle ship tasks#27
trillium merged 9 commits into
mainfrom
fm/staleness-autoclose

Conversation

@trillium

@trillium trillium commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Intent

Implement staleness auto-close: when the watcher sees a ship task's window idle past a threshold (default 2h), reclaim the process - landed work gets a full teardown, unlanded work gets a chat-only teardown that preserves the worktree and uncommitted code on disk and files a triage bead into the staleness federated store.

What Changed

  • Added idle>2h staleness auto-close to bin/fm-watch.sh: detects a ship task whose window has been idle past a configurable threshold, skips it when the crew is provably working or parked at a needs-decision/blocked gate, throttles the provably-working check to once per stale hash, retries reclaim with backoff, and continues to fire during away mode.
  • Added a --staleness-autoclose mode to bin/fm-teardown.sh that gives landed work a full teardown and gives unlanded work a chat-only reclaim that preserves the worktree/branch and files a triage bead into the staleness store; added bin/fm-staleness-file.sh to back the new per-task state files.
  • Updated bin/fm-afk-return.sh to surface staleness auto-close evidence on return from away mode, and updated AGENTS.md, docs/architecture.md, docs/configuration.md, docs/scripts.md, and the afk skill to document the new config variables and state files, with new/expanded coverage in tests/fm-teardown.test.sh and tests/fm-watch-triage.test.sh.

Risk Assessment

✅ Low: This is round 7 of iterative review on the idle>2h staleness auto-close feature; all six prior rounds' findings (ordering vs. provably-working, per-poll cost of the expensive crew-state check both pre- and post-threshold, afk gap, unbounded retry, retry-counter leak across hash changes, and the needs-decision/blocked gate bypass) have been fixed. Tracing the current code (bin/fm-watch.sh's pwf cache keyed by hash, the should_retry backoff gate, and fm-teardown.sh's landed-check/chat-only path) confirms the latest throttle fix is correctly implemented with no new regression, and the guard ordering, afk evidence logging, and retry-exhaustion fallthrough all behave as intended.

Testing

Ran both dedicated shell test suites for the staleness auto-close feature (fm-teardown.test.sh and fm-watch-triage.test.sh) end-to-end against real git worktrees and mocked backend/CLI fixtures; all 87 combined test cases passed (0 failures), directly demonstrating the landed→full-teardown path, the unlanded→chat-only-reclaim-with-preserved-worktree-and-filed-triage-bead path, and every watcher-side gating condition (threshold, provably-working, needs-decision, afk, retry exhaustion) specified in the intent. One local environment quirk (Homebrew's md5 shadowing BSD md5 -q on PATH) caused an unrelated first run to fail; fixed by prepending /sbin to PATH for the test invocation only (no source change) and reran successfully.

Evidence: fm-teardown.test.sh full run output (41/41 passed)
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 projection teardown retires its journal only after confirming the exact recorded pane is gone
ok - herdr projection teardown retains the stale journal and attempts no workspace cleanup when exact-pane close is unconfirmed
ok - squash-merged + deleted-branch worktree (PR merged) is torn down (the fix)
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 - genuine multi-commit squash merge (unmatched by ancestor or patch-id checks) is recognized as landed via 3-way merge-tree comparison
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 - dirty worktree is refused even when its committed work has landed (dirty always wins)
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 - a clean teardown kills the recorded parlay-listen pid and calls 'parlay agent-down <id>'
ok - a clean teardown still kills the recorded parlay-listen pid even when parlay is absent from PATH
ok - a beads-linked task's bead is closed automatically on a landed (non-force) teardown
ok - a beads-linked task's bead is NOT closed on a --force teardown
ok - a beads-linked task's bead is NOT closed when teardown refuses unlanded work
ok - staleness auto-close on already-landed work falls through to an ordinary full teardown
ok - staleness auto-close on unlanded work reclaims the process only, preserves the worktree, and files a triage bead
Evidence: fm-watch-triage.test.sh full run output (46/46 passed)
ok - signal_reason_is_actionable: benign absorbed, captain verbs and coalesced batches surfaced
ok - stale_is_terminal: terminal status surfaces, non-terminal and no-status are benign
ok - scan_captain_relevant_statuses lists only captain-relevant statuses
ok - classifier primitives: keyed decisions and activity phases, captain relevance, window-to-task, and overrides
ok - crew_is_provably_working: only working+run-step/pane is provable; idle/finished/parked/failed/unknown surface
ok - status_is_paused: only the leading paused verb matches, and paused is not captain-relevant
ok - crew_absorb_class: working/paused/none from one read; crew_is_paused and crew_is_provably_working agree
ok - signal_crew_provably_working: benign only when every referenced crew is provably working
ok - a no-verb signal whose crew is provably working is absorbed (no exit, no queue, suppressor advanced, beacon present)
ok - a bare turn-end whose crew is provably working (busy pane) is absorbed
ok - a bare turn-end whose crew is not provably working is surfaced (the swallowed-finish fix)
ok - a no-verb working: note whose crew is idle with no running pipeline is surfaced
ok - captain-relevant signal is surfaced (queue + exit) and marked surfaced
ok - a stale pane sitting on a terminal status is surfaced (queue + exit)
ok - a stale terminal-looking status is overridden and absorbed while a run is actively working, then wedge-escalated
ok - provably-working non-terminal stale is absorbed on first sight, then wedge-escalated past the threshold
ok - consecutive wedge escalations on the same pane accumulate and demand deep inspection at the threshold
ok - a pane becoming active again resets the consecutive wedge-escalation counter
ok - a busy worker below the turn-age bound remains working with no escalation
ok - a busy worker with a stable pane hash still escalates once its completed-turn age reaches the bound
ok - a busy worker whose pane hash changes every poll still escalates once its completed-turn age reaches the bound
ok - touching a busy worker's completed-turn marker resets the age and prevents an old-age escalation
ok - repeated busy turn-age escalations reuse the existing escalation counter and demand deep inspection at the threshold
ok - the production default busy-turn-age bound is 3600s (5min under does not wedge, 66min over does)
ok - a not-provably-working non-terminal stale is surfaced immediately (never left to wait out the timer)
ok - a declared pause is absorbed on first sight, then re-surfaced as a recheck past the threshold, never wedge-escalated
ok - exited declared-pause and captain-held panes use bounded pause cadence while a live decision gate still surfaces once
ok - a declared paused secondmate re-surfaces on the bounded normal-mode cadence
ok - a non-paused secondmate retains normal stale suppression
ok - a resumed secondmate clears pause and stale tracking before stale exemption
ok - unchanged stale hashes reclassify when a crew enters or leaves pause
ok - a declared pause is periodically rechecked against authoritative active-run state
ok - a paused status overridden by authoritative working preserves its wedge timer and escalates
ok - matching non-terminal stale suppressors repair missing or corrupt stale-since timers
ok - triage log capping handles wc byte counts with leading spaces
ok - a heartbeat with no captain-relevant change is absorbed and backs off the cadence
ok - heartbeat backstop fail-safe surfaces a captain-relevant status the per-wake path missed
ok - the liveness beacon stays fresh while the watcher absorbs benign wakes (fm-guard never false-alarms)
ok - with .afk present the watcher reverts to one-shot so the daemon owns triage (no double-triage)
ok - AFK changed paused panes hand off plain stale identities for daemon-owned pause triage
ok - a ship task idle past the auto-close threshold is reclaimed via bin/fm-teardown.sh --staleness-autoclose
ok - a ship task idle below the auto-close threshold is left to ordinary stale classification
ok - a ship task past the auto-close threshold but provably working (e.g. mid no-mistakes validation) is spared
ok - a ship task past the auto-close threshold but parked at a needs-decision gate is spared and surfaced
ok - the idle>2h auto-close backstop also reclaims during afk and logs durable evidence for the returning captain
ok - a persistently-failing reclaim retries a bounded number of times, then falls through to ordinary stale surfacing
Evidence: Filtered staleness/auto-close test results (8 tests, all passing)
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-watch-triage.test.sh.output.txt:ok - a ship task idle past the auto-close threshold is reclaimed via bin/fm-teardown.sh --staleness-autoclose
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-watch-triage.test.sh.output.txt:ok - a ship task idle below the auto-close threshold is left to ordinary stale classification
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-watch-triage.test.sh.output.txt:ok - a ship task past the auto-close threshold but provably working (e.g. mid no-mistakes validation) is spared
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-watch-triage.test.sh.output.txt:ok - a ship task past the auto-close threshold but parked at a needs-decision gate is spared and surfaced
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-watch-triage.test.sh.output.txt:ok - the idle>2h auto-close backstop also reclaims during afk and logs durable evidence for the returning captain
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-teardown.test.sh.output.txt:ok - staleness auto-close on already-landed work falls through to an ordinary full teardown
/var/folders/8k/0ll7yqm179v19qqg3qmgcq3m0000gn/T/no-mistakes-evidence/01KYZCC2GB9JFBZ4XGEA9PV7ZR/fm-teardown.test.sh.output.txt:ok - staleness auto-close on unlanded work reclaims the process only, preserves the worktree, and files a triage bead

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (6) ✅
  • ⚠️ bin/fm-watch.sh:1020 - The idle>2h staleness auto-close check fires as soon as the pane hash is stale (n>=2, not busy) and the last status line isn't paused/captain-held. It runs before the existing stale_is_terminal / pause_state_class("working") / crew_is_provably_working machinery a few lines below (bin/fm-watch.sh:1040-1122), which this codebase built specifically to avoid killing an actively-running pipeline that legitimately sits on a static pane (see the 2026-07 herdr false-surface incident comment at line ~1049: a validating crew's status log goes stale for the whole duration of a no-mistakes validation run, per AGENTS.md's sparse status-reporting contract, so status_is_paused_or_captain_held("$last") won't catch it either). Net effect: a ship task genuinely mid-validation (e.g. a slow test suite or CI wait) with no pane output for >2h will have its live process killed with zero captain notification (the reclaim is deliberately silent), before the existing "provably working" protections ever get a chance to run. Impact is bounded — unlanded work only loses the running process (worktree/branch fully preserved, a triage bead is filed) and landed work is presumably already done — but it does silently abort active-but-quiet work that the rest of this file goes out of its way to protect.

🔧 Fix: placeholder - waiting for test results before finishing
1 warning still open:

  • ⚠️ bin/fm-watch.sh:1027 - crew_is_provably_working (which shells out via FM_CREW_STATE_BIN/fm-crew-state.sh and, per its own doc comment at bin/fm-classify-lib.sh:330-333, 'may make a bounded no-mistakes call' with up to a 10s timeout) is checked BEFORE the cheap age_of &#34;$hf&#34; &gt;= STALENESS_AUTOCLOSE_SECS test in the &amp;&amp; chain at bin/fm-watch.sh:1027-1030. Because bash evaluates && left-to-right, this expensive call now runs on EVERY poll cycle (default 15s via FM_POLL) for every ship-task window that is merely idle+non-busy (n>=2), regardless of how far it is from the 2h threshold - not just once the auto-close window is actually reached. This violates the documented contract on crew_absorb_class/crew_is_provably_working ('NOT a pure read... callers run it only on no-verb signal and first-sighting stale paths, never every wake', bin/fm-classify-lib.sh:333). Every other caller in this same file guards the call behind a first-sighting/new-hash check (see bin/fm-watch.sh:1063-1074) specifically to avoid this cost. With multiple ship tasks legitimately idle between turns (a common, non-error state), this adds a serial no-mistakes subprocess call - worth up to 10s each - to every 15s watcher poll, degrading the whole fleet's supervision responsiveness. Swapping the order (cheap age check before the expensive crew_is_provably_working call) fixes it with no behavior change, since the final boolean result of an AND chain is order-independent.

🔧 Fix: Reorder staleness auto-close check to short-circuit before provably-working call
2 warnings still open:

  • ⚠️ bin/fm-watch.sh:1027 - Idle>2h auto-close is unconditionally disabled during away mode (! afk_present guard, bin/fm-watch.sh:1027) with no equivalent reclaim in the away-mode daemon (bin/fm-supervise-daemon.sh only escalates via FM_STALE_ESCALATE_SECS, it doesn't reclaim). The captain still gets a stale-wake escalation via the daemon's one-shot path, but the process is never auto-killed while away - arguably the scenario the feature's own comment ("leaving costly compute idle indefinitely") most needs to cover.
  • ⚠️ bin/fm-watch.sh:1031 - A persistently-failing staleness-autoclose reclaim retries every poll forever with no backoff or escalation counter (unlike the wedge-escalation machinery elsewhere in this file). Failures are only recorded in a write-only $STATE/.staleness-autoclose.log that nothing else reads, and the unconditional continue in the trigger block (bin/fm-watch.sh:1031-1032) means the window can never fall through to the ordinary stale/terminal surfacing path, so a stuck task with a broken reclaim has no path to ever notify the captain.

🔧 Fix: {"summary": "Run staleness reclaim during afk with retry backoff and evidence"}
1 warning still open:

  • ⚠️ bin/fm-watch.sh:412 - Staleness auto-close retry counters (.staleness-fails-$key / .staleness-next-$key) are only cleared on a successful reclaim (bin/fm-watch.sh:1094, via staleness_autoclose_clear_retries), never when the pane's hash changes (the else branch around line 1211-1212 that resets .hash-$key/.count-$key on a new hash does not call it). This contradicts the feature's own design comment, which says a stuck reclaim gives up 'until the pane's hash next changes and resets the counters via staleness_autoclose_clear_retries.' Once a task's retries are exhausted once, staleness auto-close is permanently disabled for that task for the rest of its life, even after it becomes busy again and later goes idle on an entirely new, unrelated stale period. Ordinary stale/wedge surfacing still protects the captain from silence, but the reclaim optimization silently stops working forever for that task. Fix: call staleness_autoclose_clear_retries(key) in the hash-changed branch, or otherwise reset the fail/next-retry files whenever a new hash is observed.

🔧 Fix: {"summary": "Clear staleness auto-close retries on pane hash change"}
1 error still open:

  • 🚨 bin/fm-watch.sh:1088 - The idle>2h auto-close guard only excludes a status log ending in a paused:/captain-held: verb (status_is_paused_or_captain_held, bin/fm-classify-lib.sh:135-141) and a live crew_absorb_class of working (crew_is_provably_working). It never checks whether the crew is parked at a captain-relevant gate — a needs-decision: or blocked: status line (status_is_captain_relevant, bin/fm-classify-lib.sh:100-114), which is exactly what the pre-existing stale_is_terminal path a few lines below (bin/fm-watch.sh:1116) exists to protect and surface, not silently sweep away. Trace: a ship task that hit a no-mistakes ask-user finding appends needs-decision: ... and then legitimately sits idle waiting for the captain (per section 7 of AGENTS.md, the worker "processes every synchronous return until completion" only after the captain answers — this can easily take hours, especially now that the reclaim also runs during afk per bin/fm-watch.sh:1084-1087/1109). Once idle>2h: status_is_paused_or_captain_held("needs-decision: ...") is false (its verb list is only paused/captain-held, bin/fm-classify-lib.sh:107-109/135-140), and crew_is_provably_working is false (crew_absorb_class maps run-step parked/log verb needs-decision to neither working nor paused, so it falls to its final printf &#39;none&#39;, bin/fm-classify-lib.sh:334-346). Every guard clause therefore passes and bin/fm-watch.sh:1093 calls staleness_autoclose_reclaim, which (being unlanded mid-pipeline) takes fm-teardown.sh's chat-only path: kills the live backend endpoint and deletes state/<id>.meta/.status (bin/fm-teardown.sh:459-484). The worktree survives, but the interactive process the captain's eventual decision would be sent to is gone and its meta is gone, so the normal send the same worker one exact decision ... require the matching resolved event flow (AGENTS.md section 7) can no longer be completed — the decision can only be recovered via the staleness triage bead, defeating the ask-user gate for any task idle past the threshold. This is squarely the scenario the feature's own design comment claims to exclude ("not declared paused or captain-held") but the implementation's verb check is too narrow to actually cover it.

🔧 Fix: Skip staleness auto-close when parked at needs-decision/blocked gate
1 warning still open:

  • ⚠️ bin/fm-watch.sh:1097 - crew_is_provably_working is invoked on every ~15s poll cycle (not once per distinct stale hash) for the entire time a ship task stays past the idle>2h threshold and provably working, since neither the retry-budget machinery nor a hash-change gate throttles this call the way the pre-existing terminal-stale path (bin/fm-watch.sh:1136) does. This is the same class of contract violation ('callers run it only on no-verb signal and first-sighting stale paths, never every wake', fm-classify-lib.sh:331-333) that an earlier review round flagged and fixed for the pre-threshold case, recurring here post-threshold for exactly the long-running-validation scenario the feature's own comments describe.

🔧 Fix: Throttle post-threshold provably-working check to once per stale hash
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-teardown.test.sh (41/41 passed, exit 0) — includes test_staleness_autoclose_landed_falls_through_to_full_teardown and test_staleness_autoclose_unlanded_chat_only_preserves_worktree_and_files_bead, which assert the worktree/branch/commit survive on disk and that a bead is filed to the staleness store recording the branch
  • PATH=&#34;/sbin:$PATH&#34; bash tests/fm-watch-triage.test.sh (46/46 passed, exit 0) — includes the 6 watcher-side staleness tests: fires past idle>2h threshold and calls fm-teardown.sh --staleness-autoclose; does not fire below threshold; is spared while provably working (e.g. mid no-mistakes validation); is spared and surfaced when parked at a needs-decision gate; also reclaims during afk mode with durable evidence logging; and retries a bounded number of times on persistent reclaim failure before falling through to ordinary stale surfacing
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • New Features

    • Added automatic cleanup for ordinary ship tasks idle beyond the configured threshold.
    • Preserves unlanded work, including branches, worktrees, and uncommitted changes.
    • Records reclaimed tasks for staleness triage and return-time review.
    • Added configurable retry limits, backoff settings, and teardown command overrides.
  • Bug Fixes

    • Excludes paused, actively working, and captain-held tasks from automatic cleanup.
    • Falls back to stale-task escalation after reclaim retries are exhausted.
  • Documentation

    • Updated architecture, configuration, script, and operational guidance for staleness handling.

When the watcher sees a ship task's window idle past a threshold
(default 2h, FM_STALENESS_AUTOCLOSE_SECS), it reclaims the process:
landed work gets an ordinary full teardown, unlanded work gets a
chat-only teardown that kills the backend, preserves the worktree
and uncommitted code on disk, and files a triage bead into the
staleness federated store via the new bin/fm-staleness-file.sh
helper. The never-discard-unlanded-work guard is unaffected: only
the chat process is reclaimed, never the worktree.

bin/fm-watch.sh gains STALENESS_AUTOCLOSE_SECS and the
FM_TEARDOWN_BIN test seam (matching the existing FM_CREW_STATE_BIN
pattern) so the idle backstop can invoke fm-teardown.sh in
--staleness-autoclose mode ahead of ordinary stale classification,
skipping paused/captain-held and afk-supervised windows.

bin/fm-teardown.sh gains --staleness-autoclose <idle-since>, which
reuses the existing work_is_landed check to choose between the
ordinary full teardown path and the new chat-only path.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@trillium, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9400f24e-9b2d-4af6-8f5d-2874656fe211

📥 Commits

Reviewing files that changed from the base of the PR and between 1565b46 and 1f672a1.

📒 Files selected for processing (3)
  • AGENTS.md
  • bin/fm-staleness-file.sh
  • bin/fm-teardown.sh
📝 Walkthrough

Walkthrough

The watcher adds idle staleness auto-close for eligible ship tasks. Teardown preserves unlanded work and files triage evidence. Away-mode reclaim records are reconciled on return. Documentation and tests cover configuration, behavior, retries, and evidence handling.

Changes

Staleness auto-close

Layer / File(s) Summary
Watcher eligibility and retry handling
bin/fm-watch.sh, tests/fm-watch-triage.test.sh, AGENTS.md, docs/architecture.md, docs/configuration.md
The watcher evaluates idle ship tasks, excludes protected states, retries failed reclaim attempts with capped backoff, and records successful away-mode reclaims. Tests cover eligibility, exclusions, retries, and evidence logging.
Preserving unlanded work during teardown
bin/fm-teardown.sh, bin/fm-staleness-file.sh, tests/fm-teardown.test.sh, docs/scripts.md
--staleness-autoclose performs normal teardown for landed work and preserves the worktree, branch, and changes for unlanded work. The new utility files staleness triage beads on a fail-open basis.
Away-mode evidence reconciliation
bin/fm-afk-return.sh, .agents/skills/afk/SKILL.md
Return handling collects staleness auto-close evidence and removes the log after successful reconciliation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: kunchenguid

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's primary change: staleness auto-close for idle ship tasks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/staleness-autoclose

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Line 337: Update the automatic-reclaim guidance in AGENTS.md to replace the
deleted state/<id>.status evidence with the surviving staleness bead, preserved
worktree and branch, and state/.staleness-autoclose.log. Keep the surrounding
explanation about quiet endpoints and possible automatic reclaim unchanged.

In `@bin/fm-staleness-file.sh`:
- Around line 43-48: Update the staleness create invocation in the bead_id
assignment to capture stdout only by removing the stderr redirection, while
preserving the existing failure handling and visible stderr output. Keep the
success message based solely on the returned bead ID.

In `@bin/fm-teardown.sh`:
- Around line 1270-1272: Update the conditional around
staleness_chat_only_teardown to handle [ ! -d "$WT" ] separately: report the
missing worktree without claiming it was preserved and skip the triage bead, or
route this case through the ordinary teardown path. Keep the existing chat-only
behavior for worktrees that exist but whose work is not landed.
- Around line 474-479: Update the staleness filing flow around bead_out and the
fm-staleness-file.sh invocation to detect when filing did not produce a
successful bead, including the script’s fail-open success cases. Before removing
the state files, write a durable local record containing the preserved worktree
and project location for any unfiled bead, while keeping reclamation fail-open
and retaining the existing cleanup and success behavior when filing succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ac11422-2353-4ea5-81a3-c9e141073e9e

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca2fe9 and 1565b46.

📒 Files selected for processing (11)
  • .agents/skills/afk/SKILL.md
  • AGENTS.md
  • bin/fm-afk-return.sh
  • bin/fm-staleness-file.sh
  • bin/fm-teardown.sh
  • bin/fm-watch.sh
  • docs/architecture.md
  • docs/configuration.md
  • docs/scripts.md
  • tests/fm-teardown.test.sh
  • tests/fm-watch-triage.test.sh

Comment thread AGENTS.md Outdated
Comment thread bin/fm-staleness-file.sh
Comment thread bin/fm-teardown.sh
Comment thread bin/fm-teardown.sh Outdated
- fm-teardown.sh: write $ID.staleness-unfiled with worktree/branch/project
  before removing $ID.meta when staleness bead filing fails, so a preserved
  unlanded worktree never loses its location pointer
- fm-teardown.sh: report a missing worktree accurately instead of claiming
  'worktree preserved' when $WT does not exist
- fm-staleness-file.sh: capture only stdout for $bead_id, leave the
  'staleness create' call's stderr unmerged
- AGENTS.md: point reclaim evidence at the filed bead / staleness-unfiled
  fallback, preserved worktree, and .staleness-autoclose.log instead of the
  deleted $ID.status file
@trillium
trillium merged commit e827912 into main Aug 1, 2026
11 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.

1 participant