Skip to content

feat(bin): close exited ship/scout panes as ordinary cleanup - #2970

Open
nbost130 wants to merge 9 commits into
kunchenguid:mainfrom
nbost130:fm/fm-close-exited-panes
Open

feat(bin): close exited ship/scout panes as ordinary cleanup#2970
nbost130 wants to merge 9 commits into
kunchenguid:mainfrom
nbost130:fm/fm-close-exited-panes

Conversation

@nbost130

Copy link
Copy Markdown

Intent

Close a finished worker's pane as ordinary cleanup once the agent has actually exited, even if the PR is still open.

Today cleanup stops the agent and, after a merge, returns the copy. It does not close a finished worker that already quit while its PR is still open. The empty pane keeps alarming. Current leftover panes were already closed by hand; this ship makes pane-close ordinary going forward.

Acceptance:

  • When a ship/scout worker process is confirmed exited (not merely idle), close that worker's backend pane without waiting for merge or teardown.
  • Do not discard unlanded work. Do not return the treehouse/copy until landed teardown (or explicit discard).
  • Do not close a pane that still has a live agent, a pending composer that is not empty, or uncommitted work you would lose by closing the shell.
  • Closing the pane must not require firstmate to poke, and must not trip stale/empty-pane alarms afterward.
  • Keep Herdr focus-preserving close contracts; never close the captain's active tab; never issue Herdr lifecycle against the fleet default session except through the named lab helper.
  • Tests cover: exited + PR still open => pane closed, copy retained; live agent => pane kept; teardown-after-merge still returns the copy.
  • Prefer extending existing cleanup rather than a new control plane. One-owner rule: script header owns flags/mechanics.
  • Owner scripts: bin/fm-teardown.sh, bin/fm-herdr-session-cleanup.sh, bin/backends/herdr.sh, and tmux/zellij/orca equivalents if the same gap exists.
  • Ship through no-mistakes to a PR. Do not merge.

What Changed

  • bin/fm-teardown.sh gains a --close-pane mode: ordinary cleanup that closes a finished (PR recorded, done:/failed: status, or scout report.md) ship or scout worker's backend pane once the recovery-grade agent-state classifier confirms the agent has actually exited. It refuses on a live or unreadable agent state, a pending composer, secondmate tasks, unverified backends (e.g. orca), or a Herdr pane that is the captain's active tab; on success it records pane_closed=1 on the task meta without returning the isolated copy or discarding any work, and --close-pane/--force cannot be combined.
  • bin/fm-watch.sh's watch loop now auto-invokes fm-teardown.sh --close-pane for finished ship/scout windows whose agent state is dead/missing, skipping the normal stale-window handling for them; both fm-watch.sh (recorded_windows) and bin/fm-session-start.sh treat panes with pane_closed=1 as already closed instead of probing them or raising stale/missing-endpoint alarms.
  • Adds tests/fm-teardown-close-pane.test.sh, updates tests/fm-session-start.test.sh and bin/fm-test-run.sh test routing, and updates docs (AGENTS.md, docs/agent-control.md, docs/architecture.md, docs/configuration.md, docs/scripts.md, .agents/skills/stuck-crewmate-recovery/SKILL.md) to describe the new cleanup path.

Risk Assessment

⚠️ Medium: The change is well-scoped, reuses existing recovery-grade classifiers and locking, and its behavioral tests (real tmux + real teardown script) correctly cover exited+PR-open, live-agent, unfinished-exit, and post-close-teardown cases; the one substantive issue is a fail-open composer check that deviates from the codebase's established fail-safe convention for the same classifier and could, on a transient read failure, close a pane with unproven pending composer text.

Testing

The new fm-teardown.sh --close-pane behavior is directly and thoroughly demonstrated end-to-end via real tmux panes in tests/fm-teardown-close-pane.test.sh, all 5 cases passing and matching every acceptance criterion (exited+PR-open closes/retains copy, live agent keeps pane, unfinished exit keeps pane, teardown-after-merge still returns copy, watcher skips closed panes). While validating the companion fm-session-start.test.sh coverage, I found the new test_endpoint_liveness_closed_pane itself was broken (missing required argument to a helper, crashing under set -u) and fixed it with a one-line argument addition; it now passes both in isolation and as part of the full file, alongside all other session-start tests once two unrelated pre-existing environment failures (verified present on the base commit too) are set aside.

  • Outcome: ⚠️ 1 info across 1 run (10m18s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • ⚠️ bin/fm-teardown.sh:2445 - teardown_composer_blocks_pane_close only refuses to close the pane when fm_backend_composer_state returns pending or pending-unproven, letting an unknown verdict fall through and permit the close. Everywhere else this classifier is consumed (fm_pane_input_pending in bin/fm-tmux-lib.sh:194-196, and the classifier's own contract comment 'positive proof required for empty ... unrecognized future verdicts failing safe'), unknown is treated as not-provably-empty and blocks the action. fm_tmux_composer_state legitimately returns unknown on a transient cursor-row or capture read failure (bin/fm-tmux-lib.sh:141-143), which can coincide with an agent_state=dead pane (agent process exited, shell still up) — exactly the situation --close-pane targets. In that case the pane closes without ever proving the composer was empty, which can discard unsubmitted composer text and contradicts the stated acceptance criterion 'Do not close a pane that ... has a pending composer that is not empty.' Recommend failing closed: block the close on any verdict other than the proven empty (i.e. case "$verdict" in empty) return 1 ;; *) return 0 ;; esac), matching the fail-safe convention already established for this same classifier elsewhere in the codebase.
⚠️ **Test** - 1 info
  • ℹ️ tests/fm-session-start.test.sh:1352 - Fixed a real test bug in the newly added test_endpoint_liveness_closed_pane (tests/fm-session-start.test.sh): it called make_fake_tmux "$fakebin" with only one argument, but that helper requires a second (live-target) argument, and the file runs under set -u, so the test crashed with "$2: unbound variable" before any assertions ran. Fixed by passing a placeholder live-target string; harmless since pane_closed=1 short-circuits before tmux liveness is ever checked. Verified the fix in isolation and against the full file with two confirmed pre-existing/environment-only failures (test_output_ordering_diagnostics_lead, test_composition_invokes_real_scripts's node-detection line) skipped, reproducing identically on the base commit — confirming they predate this change.
  • bash tests/fm-teardown-close-pane.test.sh (real tmux-backed behavioral coverage: exited+PR-open closes pane/retains copy, live agent keeps pane, unfinished exit keeps pane, teardown-after-close still returns copy, recorded_windows skips closed panes)
  • isolated invocation of test_endpoint_liveness_closed_pane from tests/fm-session-start.test.sh (endpoint: closed reporting for a pane_closed meta) before and after the test-bug fix
  • full tests/fm-session-start.test.sh with two confirmed pre-existing/environment-only failing tests skipped, to confirm all other 99 tests (including the new one) pass cleanly
  • cross-check against base commit 038d0f7 to confirm the two skipped failures (test_output_ordering_diagnostics_lead, node-detection line in test_composition_invokes_real_scripts) are pre-existing and unrelated to this change
⚠️ **Document** - 1 info
  • ℹ️ docs/herdr-backend.md:124 - docs/herdr-backend.md documents that ordinary (non-projected) full teardown performs a plain close even on the captain's active tab, while the new --close-pane path refuses to close on the captain's active tab unconditionally. This isn't a contradiction (herdr-backend.md describes only the pre-existing full-teardown/projected-cleanup paths, which are unchanged), and the new invariant is already fully owned by fm-teardown.sh's header comment per the repo's mechanics-ownership tier. Flagging only as a judgment call in case a future consolidation of Herdr close-contract documentation is wanted.
⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

Mithrandir System added 2 commits August 24, 2026 12:02
A finished worker that has already quit left an empty pane until merge
teardown. Close that pane as ordinary cleanup once the agent is
confirmed gone, keep the isolated copy, and skip stale/missing-endpoint
recovery until landed teardown returns the copy.
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The PR is not yet safe to merge because sufficiently long wrapped pending input can still be mistaken for an empty prompt and discarded when its pane closes.

The joined-capture fix only examines the final 20 physical rows, allowing a longer command to lose its original prompt glyph at the capture boundary and leave a tail that passes the pane-close emptiness proof.

Files Needing Attention: bin/fm-teardown.sh, bin/fm-composer-lib.sh, tests/fm-teardown-close-pane.test.sh

Reviews (8): Last reviewed commit: "no-mistakes: apply CI fixes" | Re-trigger Greptile

Comment thread bin/fm-teardown.sh Outdated
Comment thread bin/fm-watch.sh Outdated
Comment thread bin/fm-composer-lib.sh
Comment thread bin/fm-teardown.sh
Comment thread bin/fm-composer-lib.sh
Comment thread bin/fm-backend.sh
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Scheduled 11:10am PT 8/24 pass. VISION.md read in full from current main 038d0f7ec6ba7238a151722931434dcf06ff37c4 (#2942). FIRST LOOK. No competing PR. Never messaged the captain.

VISION (inspected bin/fm-watch.sh close_exited_panes_enabled / close_finished_exited_pane, bin/fm-teardown.sh --close-pane, tmux joined capture, Herdr active-tab refuse, tests/fm-watch-close-exited-panes-optin.test.sh). Per-rule: authority-is-explicit aligns (auto-close stays inert until config/close-exited-panes exists or FM_CLOSE_EXITED_PANES=1; FM_CLOSE_EXITED_PANES=0 forces off). Unlanded work is never torn down aligns (copy retained; pane_closed=1 only). Scripts-own-mechanics aligns (teardown owns the close). Restart-is-a-non-event aligns (durable meta). Peace-of-mind aligns when opted in (empty panes stop alarming). Delegation spine aligns.

Class: opt-in. Watcher auto-close is gated. --close-pane is an explicit mode, not a silent default. Not default-behavior.

Security: none. No workflow-file / secret / injection. Herdr close refuses the captain's active tab and unverified backends. Composer pending/unproven and classifier failure block close (fail-closed). Greptile still flags Herdr wrapped-row capture (tmux got -J; Herdr falls back to row-oriented capture) — not a merge gate.

Overlap / HOLD: bin/fm-teardown.sh also in standing holds #2637 / #2692 / #2804. bin/fm-watch.sh overlaps many open PRs (#2953, #2914, #2877, #2867, #2809, #2799, #2796, #2701, …). Not fm-spawn.sh / herdr.sh / lock-lib. Hold overlap on teardown blocks auto-merge even if CI/NM later go green.

CI / NM: HEAD 1bfa191212c097ba3097179ffd5d699c83c0b6b0. mergeable MERGEABLE, mergeStateStatus UNSTABLE. ahead 7 / behind 0. Body no-mistakes-pipeline-attestation:v1 names 9b1424ab9a48946a08f6adc7bcd47113f4f94c0d, not THIS HEAD (later no-mistakes: apply CI fixes). nbost130 has other open firstmate PRs (#1718, #1524) and no merged PR; THIS HEAD's fork workflows were action_required. Approved this pass after full diff review. No security issue.

Workflows approved this pass: CI 32757973617, Require no-mistakes 32757973668. Not green at comment time.

Land-eligible rec: NO (standing teardown-hold overlap; NM attestation mismatch; CI not yet green). Captain-flag NOW: no.

Waiting-on-author to restamp no-mistakes for THIS HEAD. Not a captain-decision hold on class (opt-in is fine). Standing holds still prevent auto-merge.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: first look. First-time fork CI reviewed and approved this pass (no .github / secrets).

class=opt-in. config/close-exited-panes or FM_CLOSE_EXITED_PANES=1; inert otherwise.

VISION.md: authority aligns (explicit grant). Honest interface aligns (empty leftover panes are not news). Scripts align. Restart aligns. Spine aligns. Vendor mixed (composer empty-proof). Scope aligns.

This HEAD: a4063f5f6804d22facbd1f700d840a61d5c5392a. MERGEABLE / UNSTABLE, ahead 7 / behind 0.
Attestation 9b1424ab… THIS HEAD. Workflow-approval this pass: CI 32762348626, Require no-mistakes 32762348614.

Hold files: bin/fm-teardown.sh, bin/fm-watch.sh, docs/architecture.md overlap standing spawn/teardown/watcher holds. Not auto even if CI+NM go green.

Waiting on author for a HEAD-matching attestation. The file-overlap hold is coordinator, not a captain ping.

Comment thread bin/fm-teardown.sh
# time and could pass it wrongly (Greptile P1: task fm-close-exited-panes
# review - Herdr reaches this same fallback through an unjoined capture).
fm_backend_capture_joined_reliable "$BACKEND" || return 0
cap=$(fm_backend_capture_joined "$BACKEND" "$T" "${FM_COMPOSER_CAPTURE_LINES:-20}" 2>/dev/null) || return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Capture boundary hides pending input

When an exited worker has an unsubmitted command spanning more than 20 physical terminal rows, the bounded joined capture omits the original prompt and can leave a tail ending in one shell glyph. The fallback then treats that truncated tail as an empty prompt and closes the pane, discarding the unsubmitted command.

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