Skip to content

fix(bin): guard missing backend adapters and prune merged task branches - #2981

Closed
trevorallred wants to merge 14 commits into
kunchenguid:mainfrom
trevorallred:fm/firstmate-herdr-preflight-missing-adapter-bug
Closed

trevorallred wants to merge 14 commits into
kunchenguid:mainfrom
trevorallred:fm/firstmate-herdr-preflight-missing-adapter-bug

Conversation

@trevorallred

Copy link
Copy Markdown

Intent

Diagnose and fix the failure independently reproduced on a clean, unmodified main by ./bin/fm-test-run.sh tests/fm-teardown.test.sh at herdr-preflight-missing-adapter. Establish exactly why teardown appears to continue instead of refusing under the simulated missing-Herdr-adapter condition. Compare against and confirm the sibling missing-parser and missing-explicit-close-helper modes, and use their earliest divergence, relevant history, counterfactual testing, and disconfirming evidence to distinguish an actual runtime preflight gap from stale test simulation. If runtime preflight has a real gap, fix it as a serious teardown safety check. Change the test simulation only if actual runtime behavior is first proven genuinely safe, rather than assuming the implementation is right. Add whatever regression coverage is missing for the real cause. Do not start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior because herdr-lab was not enabled.

What Changed

  • Check backend adapter readability before sourcing so a missing Herdr adapter returns a controlled preflight failure instead of terminating teardown unexpectedly.
  • Add a shared proof-based cleanup helper and run a fleet-sync backstop that prunes only un-checked-out fm/* branches already merged into the local default branch, including local-only and no-origin projects.
  • Consolidate teardown task-branch removal and document the adapter refusal and branch-pruning behavior.

Risk Assessment

✅ Low: The missing-adapter path now fails through a guarded return so teardown’s existing prerequisite check can refuse before destructive work, while sibling prerequisite guards and behavioral regression coverage remain intact.

Testing

Focused teardown and backend harnesses passed. The teardown matrix exercised missing adapter, parser, and explicit-close-helper prerequisites and verified refusal preserves the worktree, branch, records, and avoids pane-close/return actions; the manual transcript demonstrates the causal runtime boundary—an absent adapter returns failure to its caller rather than terminating the shell. No real Herdr lifecycle was driven.

Evidence: Missing adapter preserves caller control

Source: Missing adapter preserves caller control

missing-adapter adapter-source exit=23 CALLER_CONTROL_RETAINED

missing-adapter adapter-source exit=23
CALLER_CONTROL_RETAINED

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⚠️ **Rebase** - 1 warning
  • ⚠️ .agents/skills/afk/SKILL.md - branch carries 10 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (126 file(s)) into the PR:
  • a5a2396 fix(bin): make the merged-branch backstop sweep default-on again
  • 2fcb5c0 no-mistakes: apply CI fixes
  • 81aa22a no-mistakes: apply CI fixes
  • 39c4249 no-mistakes: apply CI fixes
  • 0b6229f no-mistakes: apply CI fixes
  • 418e88f no-mistakes: apply CI fixes
  • 200844e no-mistakes: apply CI fixes
  • 0c82024 no-mistakes(document): Document merged task branch cleanup
  • d762fd4 no-mistakes(review): Prevent unsafe gone-upstream branch deletion
  • fcf2f51 fix(bin): drop safely-merged fm/<task-id> branches, add a backstop sweep

Push main to origin, or rebase your branch onto origin/main, before gating.

🔧 Fix applied.
1 warning still open:

  • ⚠️ .agents/skills/afk/SKILL.md - branch carries 10 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (126 file(s)) into the PR:
  • a5a2396 fix(bin): make the merged-branch backstop sweep default-on again
  • 2fcb5c0 no-mistakes: apply CI fixes
  • 81aa22a no-mistakes: apply CI fixes
  • 39c4249 no-mistakes: apply CI fixes
  • 0b6229f no-mistakes: apply CI fixes
  • 418e88f no-mistakes: apply CI fixes
  • 200844e no-mistakes: apply CI fixes
  • 0c82024 no-mistakes(document): Document merged task branch cleanup
  • d762fd4 no-mistakes(review): Prevent unsafe gone-upstream branch deletion
  • fcf2f51 fix(bin): drop safely-merged fm/<task-id> branches, add a backstop sweep

Push main to origin, or rebase your branch onto origin/main, before gating.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • ./bin/fm-test-run.sh tests/fm-teardown.test.sh
  • ./bin/fm-test-run.sh tests/fm-backend.test.sh
  • Manual isolated missing-adapter adapter-source invocation, recorded in missing-adapter-runtime-evidence.txt
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

fm-teardown.sh already dropped a task's own fm/<task-id> branch inline once
its work was confirmed landed, but a local-only-mode (or origin-less) project
had no backstop for a branch that survived past that point - fm-fleet-sync.sh's
existing periodic prune only recognized a squash-merged PR's now-gone remote
branch, and skipped the whole remote-backed sync (including that prune) for
exactly the projects with no remote to notice "gone" tracking on. That gap
matched a concrete trigger: five rapid local-only ship tasks against a
local-only project each left their fm/<task-id> branch behind even though
bin/fm-merge-local.sh had already fast-forward-merged every one of them, and
firstmate cleaned them up by hand with `git branch -d`.

Add bin/fm-branch-merge-lib.sh, the one owner of "is this branch provably safe
to delete": not checked out in any worktree, and either an ancestor of the
merged-into ref (a clean fast-forward or non-squash merge - git's own safe
`branch -d` can verify this itself) or its upstream tracking reads "[gone]"
(a squash-merged PR's remote branch was deleted). Never force-deletes past
that proof.

fm-fleet-sync.sh gains prune_merged_fm_branches, a git-only sweep of a
project's own fm/* branches using that shared proof, run unconditionally
before any mode/remote gate - so it also covers local-only and no-origin
projects, which the existing remote-backed prune_gone_branches never reaches.
fm-teardown.sh's own inline branch-drop is refactored (no behavior change)
into one local helper instead of two duplicated copies.

Regression coverage: fm-fleet-sync.test.sh gains cases proving the sweep
prunes a genuinely fast-forward-merged fm/* branch (including with no origin
remote at all), leaves an unmerged/diverged branch and one still checked out
in an active worktree untouched, and never targets the project's own default
branch. fm-teardown.test.sh's existing local-only-merged and
no-pr-recorded/externally-merged-PR cases now also assert the task branch is
actually dropped, closing the concrete trigger and confirming today's
externally-merged-PR reconciliation already covers that case end to end.
The prior CI fix round gated prune_merged_fm_branches behind a new
FM_FLEET_PRUNE_MERGED opt-in (default off), responding to an automated
review concern that a destructive fleet-sync mutation should not default
on. That concern does not hold here: fm-fleet-sync.sh is AGENTS.md's own
named exception to "never write to a project" (fleet sync, secondmate
sync, and a few other guarded paths are explicitly carved out), and
prune_gone_branches in this exact file already deletes local branches
from a project clone by default, gated only by the pre-existing
FM_FLEET_PRUNE variable. prune_merged_fm_branches extends that same,
already-authorized mechanism to close a coverage gap (local-only and
no-origin projects), not new destructive authority - so it belongs under
the same default-on gate, matching the concrete trigger this whole change
exists to fix (a task branch left behind with nothing to notice or clean
it up automatically).

Reverts the gate to FM_FLEET_PRUNE (default on, matching
prune_gone_branches), removes every FM_FLEET_PRUNE_MERGED reference from
comments and docs, and drops the now-inapplicable
test_merged_task_branch_requires_explicit_prune_authority test along with
its run_sync_with_merged_prune helper, restoring the other prune-positive
tests to plain run_sync. The atomic branch-d-from-a-detached-worktree
delete mechanism, the expected-tip and worktree-race regression tests, and
the [gone]-without-merge test from the intervening CI fix rounds are kept
unchanged - only the gating variable and the prose/tests describing it
move back to default-on.
@trevorallred
trevorallred force-pushed the fm/firstmate-herdr-preflight-missing-adapter-bug branch from e03e4f1 to 07cab7f Compare August 24, 2026 21:45
@greptile-apps

greptile-apps Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

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

Comment thread bin/fm-fleet-sync.sh Outdated
Comment thread bin/fm-fleet-sync.sh Outdated
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: first look on current main 038d0f7ec6ba (#2942). Never messaged the captain.

class=default-behavior (mixed PR). (a) missing-adapter [ -r "$adapter" ] before . is corrective. (b) prune_merged_fm_branches runs on every fleet-sync with FM_FLEET_PRUNE defaulting to 1, including local-only/no-origin, and deletes fm/* refs whose tip is an ancestor of local default. Mixed + destructive is never auto.

VISION.md (inspected bin/fm-backend.sh source guard, bin/fm-branch-merge-lib.sh, bin/fm-fleet-sync.sh prune_merged_fm_branches, bin/fm-teardown.sh teardown_drop_task_branch + earlier herdr preflight):

  • One captain, one interface: aligns for the adapter refusal (honest teardown refuse). does not align as auto for silent default-on branch deletion.
  • Authority is explicit: does not align (destructive prune ships on by default; FM_FLEET_PRUNE=0 is an opt-out, not an opt-in). Merging/discarding/destructive work needs the captain's explicit word.
  • Scripts own the mechanics: aligns (ancestor proof, worktree/default-branch guards, race tests).
  • A restart is a non-event: aligns for the adapter guard. Unlanded work is never torn down: mixed (proof tries to avoid unmerged deletes; squash-merged tips are left; still deletes refs without a captain word).
  • Delegation with a spine: cannot tell as auto (two unrelated intents in one PR, including a retry of closed fix(bin): prune merged task branches across all project modes #2768).
  • The fleet outlives any vendor: aligns.
  • Scope: aligns.

This HEAD: 8956ed3e0e42b376b44dd78d40d9ac1ef582a9f3. MERGEABLE / UNSTABLE, ahead 13 / behind 0.
Attestation e03e4f13bc0118320bd9b40bac31821debf7312c ≠ THIS HEAD (SHA is not even in this PR's commits).
CI action_required: CI 32782786733, Require no-mistakes 32782786738. Not a first-time fork (trevorallred has closed #2768 / #2723); workflows not approved this pass. Greptile is not a merge gate.

Security: none in the adapter guard. Prune is git-ref deletion, fail-safe on uncertainty. Overlap / holds: bin/fm-teardown.sh also in open #2804 (pool isolation), #2637 (herdr pair), #2692 (herdr pair). Docs overlap docs/herdr-backend.md / docs/configuration.md. Not a lock PR.

Land-eligible rec: NO. Captain-flag NOW: no (NM mismatch is an author/CI blocker; mixed destructive is also a product call, but do not escalate while attestation is wrong).

This is a captain-decision on whether a default-on merged-branch sweep should land, preferably split from the adapter guard. It is also waiting-on-author for a HEAD-matching attestation and to unmix the two intents. Not a merge I will recommend.

@trevorallred

Copy link
Copy Markdown
Author

Redirecting to the correct repo: https://github.com/trevorallred/firstmate/pull/1

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