Skip to content

fix(bin): treat a torn-down blocker as resolved in the fleet snapshot - #25

Merged
rub-a-dub-dub merged 1 commit into
mainfrom
fm/firstmate-stale-blocker-links-hide-decisions
Sep 22, 2026
Merged

rub-a-dub-dub merged 1 commit into
mainfrom
fm/firstmate-stale-blocker-links-hide-decisions

Conversation

@rub-a-dub-dub

Copy link
Copy Markdown
Owner

Intent

Four captain decisions had been hidden for two days: each is linked blocked-by a task whose blocker has already landed, but the bearings digest buckets any hold with an unresolved blocker as blocked, keeping it out of Captain's Call.

What I found

Per the assigned scope, I first verified each of the four blocked-by links against reality:

  • firstmate-retain-report-single-shot blocked-by firstmate-lint-debt-blocking-prs - the blocker genuinely landed (PR chore: annotate pre-existing shellcheck findings that fail CI lint #10, merged and an ancestor of main; three later merges all show Lint passing clean).
  • firstmate-unreplayable-backlog-close blocked-by firstmate-lint-debt-blocking-prs - same, genuinely landed.
  • cowork-skills-directory-phone-design-forks blocked-by cowork-skills-directory-selfcontained-folder - genuinely landed in claude-cowork-skills (PR feat: add crash and reboot resilience for firstmate kunchenguid/firstmate#51, merged, ancestor of that repo's main).
  • firstmate-answered-arm-incomplete-cleanup blocked-by firstmate-retain-report-single-shot - not landed; still an active, unfinished, unmerged task. Correctly still blocked - left alone.

That pointed at data/backlog.md, but that file is captain-private runtime state, not tracked in this repo, so there was nothing to ship there. Digging into why the digest still showed all three as blocked despite tasks-axi itself already resolving them correctly (blocked_by: none) found the real bug: bin/fm-fleet-snapshot.sh builds its own independent, regex-based view of data/backlog.md rather than using tasks-axi's resolver. Its $resolved_ids map only marks a blocker resolved when that blocker's own row is still present in the file with state == done. Once a landed blocker is torn down or ages out of the Done retention window - which is exactly what happened to both named blockers here - its id simply disappears from the map, and every row still naming it reads as permanently unresolved rather than long-since-resolved.

Fix

One-line change to the unresolved_blocker_ids filter in bin/fm-fleet-snapshot.sh: a blocker only counts as unresolved when a record for it exists and isn't done. An absent record (already torn down) now resolves, matching tasks-axi's own semantics, so the two readers of one backlog can no longer disagree. Updated the file's own header comment (the single documented owner of this contract) to match.

Kept the fix bounded to the resolver disagreement rather than unifying fm-fleet-snapshot.sh onto tasks-axi generally - that script's raw-markdown parsing covers many other fields (title, hold reason, links, etc.) and a full unification reaches well past this defect.

Tests

Several existing fixtures exercised the old behavior directly, or used a nonexistent blocker id as a stand-in for a genuinely still-open external dependency (which this fix now also resolves). Updated those to either match the corrected semantics or given the "still open" ids a real, undone row so the scenario they're testing stays genuine. Full runs of every test file touching this code path (fm-bearings-snapshot, fm-fleet-snapshot-view, fm-captain-hold-lifecycle, fm-classify-decision-key, fm-home-summary-refresh, fm-remote-secondmate-lifecycle-e2e, fm-secondmate-reconcile, fm-remote-backlog-handoff, fm-secondmate-lifecycle-e2e, fm-session-start) pass, plus a full CI=true bin/fm-lint.sh.

Verified the actual outcome against the live backlog, not just the code: after the fix, all three genuinely-unblocked holds - including the phone-layout decision pair - resolve to hold_bucket: live / captain_actionable: true and appear in decisions_open, while the fourth (still genuinely blocked) correctly stays a gate.

Scope notes

  • Did not touch data/backlog.md - it's outside this worktree and captain-private; the resolved state there was already correct, nothing needed clearing.
  • Did not build general stale-blocker-link detection - this is a bounded correctness fix to one existing resolver, not a new proactive scanner.

@rub-a-dub-dub
rub-a-dub-dub force-pushed the fm/firstmate-stale-blocker-links-hide-decisions branch from 8d9d5d4 to a97253d Compare September 22, 2026 16:32
fm-fleet-snapshot.sh's blocked-by resolver only marked a blocker
resolved when that blocker's own row was still present in backlog.md
with state=done. Once a landed blocker was torn down or aged out of
Done retention, its id simply vanished from the lookup and every row
still naming it read as permanently unresolved, disagreeing with
tasks-axi's own resolver (which correctly treats a missing blocker id
as no longer blocking) and hiding the dependent captain holds behind
a disclosed gate instead of surfacing them as decisions.

Update the affected fixtures: several exercised the old behavior
directly (asserting a nonexistent blocker id stays "blocked" forever)
or used a nonexistent id as a stand-in for a genuinely still-open
external dependency, which the fix now resolves too - give those a
real, undone row so the scenario they're testing stays genuine.
@rub-a-dub-dub
rub-a-dub-dub force-pushed the fm/firstmate-stale-blocker-links-hide-decisions branch from a97253d to d57de31 Compare September 22, 2026 20:58
@rub-a-dub-dub
rub-a-dub-dub merged commit d87dc0f into main Sep 22, 2026
13 of 14 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