fix(dashboard): wake firstmate when the Admiral writes a note on a card - #39
Merged
Merged
Conversation
A note he writes in the communication tab used to sit unread until a session happened to look, the same way an unwoken approval did before. Publish a `check: dashboard-note <card-id>` wake through the existing approval publisher when the note's author is admiral; agent-authored notes publish nothing, and a wake-publish failure never costs him the note itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZEpZ1SxCyt5n9ChfaMcEX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Bug: on 2026-09-07 the Admiral wrote "is this still needing attention?" on a board card as a communication note and nobody answered for 2.6 hours, because the board only wakes firstmate when he approves a plan, never when he writes a note.
Fix: in the note handler (bin/fleet-dashboard/server/store.py add_note), when the note's author is "admiral", publish one durable wake through the existing bin/fleet-dashboard/server/wake.py path (publish_check_wake, which calls fm_wake_append) - the same publisher the approval path already uses, not a hand-written queue append. The wake is shaped like the approval wake:
check: dashboard-note <card-id> - he wrote on the card; read and answer it. Agent-authored notes must publish nothing. A wake-publish failure must never fail the note write - the note is his either way - and mirrors exactly how the approval path (_publish_approval_wake) handles the same failure: log loudly to stderr and record an audit finding, but never raise back to the caller.Also updated the trigger documentation so this wake is discoverable: .agents/skills/fleet-dashboard/SKILL.md now documents that a note on the communication tab wakes firstmate with
check: dashboard-note <card-id>, mirroring the approval wake documentation already there, and AGENTS.md section 13's one-line fleet-dashboard entry now also names the dashboard-note wake meaning (the Admiral wrote on that card; answer on the card in that turn).Tests added to tests/fm-dashboard.test.sh, following the existing approval-wake test pattern exactly:
show) even when the wake queue directory is made unwritable, proving publish failure never costs the noteConstraints observed: never write the wake queue directly - only through wake.py's existing publish_check_wake/fm_wake_append path; never touch the live systemd-managed board process; this is firstmate's own shared tracked material (bin/, .agents/skills/, AGENTS.md, tests/) so firstmate-coding-guidelines applies - ran bin/fm-lint.sh (shellcheck clean) and bin/fm-doc-audience-check.sh (clean) before considering the work done.
What Changed
Store.add_noteinbin/fleet-dashboard/server/store.pynow calls a new_publish_note_wakewhen the note's author isadmiral, publishing one durablecheck: dashboard-note <card-id> - he wrote on the card; read and answer itrecord through the existingpublish_check_wakepath inwake.pyafter the note commits. Agent-authored notes publish nothing. AWakePublishErrornever fails the note write: it is logged to stderr and recorded as anote-wake-unpublishedaudit finding on the card, mirroring_publish_approval_wake.fleet-dashboardskill's communication-tab entry, thefleet-dashboardline in AGENTS.md section 13, thenotecommand's--authorusage inbin/fm-dashboard.sh, and a new "A note he writes wakes firstmate the same way" paragraph plus the discrepancy-log description indocs/dashboard.md.wake.py's module and function docstrings now describe both the approval and note wakes.tests/fm-dashboard.test.shfollowing the approval-wake pattern: an admiral note publishes exactly one wake record naming the card, an agent note publishes no wake record, and a note posted via the HTTP API is still saved (HTTP 201, text visible inshow) with anote-wake-unpublishedfinding recorded when the state directory is made unwritable.Risk Assessment
✅ Low: The change is a small, well-bounded addition that reuses the existing approval-wake publisher and failure handling verbatim, both prior-round fixes are verified present and correct in the current code, the UI's note write sends author "admiral" so the reported failure sequence is covered, and the tests assert observable persisted state through the public HTTP and CLI surfaces.
Testing
Ran the targeted dashboard test file at the target commit (all pass, including the three new note-wake cases), reproduced the bug by running the same new tests against the base commit (fails: admiral note publishes no wake), and performed a manual end-to-end pass against a real board process showing the wake record in firstmate's queue, firstmate's own drain reading it back, agent notes staying silent, and the unwritable-queue case saving the note while logging loudly to stderr and the board's discrepancy log (captured in a CLI transcript and a screenshot of the rendered board). No failures or missing evidence.
Evidence: CLI/API transcript: admiral note wake, drain read-back, agent silence, unwritable-queue failure path
Source: CLI/API transcript: admiral note wake, drain read-back, agent silence, unwritable-queue failure path
Evidence: Base-commit reproduction: new tests fail before the fix
Source: Base-commit reproduction: new tests fail before the fix
not ok - his note published no wake record for the card he wrote onEvidence: Wake record published by an admiral note (from the transcript)
Evidence: tests/fm-dashboard.test.sh at target commit
Source: tests/fm-dashboard.test.sh at target commit
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed ✅
tests/fm-dashboard.test.sh:1448- test_a_note_is_saved_even_when_the_wake_cannot_be_published relies onchmod 000 $FM_HOME/stateto make the wake publish fail, but never asserts the failure actually happened (no check that the wake queue lacks adashboard-note:<id>record or that anote-wake-unpublishedaudit finding was recorded). When the suite runs as root, chmod 000 is ignored, the wake publishes normally, and the test passes without exercising the failure path it claims to prove. The repo already has a convention for this (tests/fm-dashboard-card-link.test.sh:1740 skips permission-based cases under root with an explicitpass "skipped ..."). Add the same[ "$(id -u)" -ne 0 ] || { pass ...; return 0; }guard and, after restoring the directory, assert the queue has nodashboard-note:$idline (and/or that the card's audit finding with key note-wake-unpublished exists) so the test distinguishes 'saved despite failure' from 'saved, nothing failed'. Note also this path costs ~5s per run because fm_lock_acquire_wait spins until wake.py's PUBLISH_TIMEOUT_SECONDS kills it - acceptable, but worth a comment so nobody mistakes it for a hang.bin/fleet-dashboard/server/wake.py:5- Two prose statements are now false after this change: the module docstring of bin/fleet-dashboard/server/wake.py still says nothing the board records reaches firstmate 'except his approval, which is the one board write that hands work BACK to the fleet', and docs/dashboard.md's discrepancy-log paragraph (around line 326) lists 'an approval whose wake to firstmate could not be published' as the only wake-failure row shape, while the newnote-wake-unpublishedfinding now also lands there. SKILL.md and AGENTS.md were updated per intent; these two were not. Both are one-line fixes.bin/fleet-dashboard/server/store.py:924- _publish_note_wake is a near-verbatim copy of _publish_approval_wake (try publish_check_wake / catch WakePublishError / stderr headline / record_audit_finding guarded by a broad except). A small shared helper taking (key, payload, headline, audit_text, audit_key) would collapse both to a few lines each and guarantee the two failure paths stay identical, which is the invariant the intent asks for. The intent explicitly chose mirroring, so this is a follow-up refactor, not a blocker.🔧 Fix: assert note-wake failure in test; refresh stale wake docs
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-dashboard.test.shat target commit 16c008c: all cases pass including the three new note-wake tests (test_his_note_leaves_firstmate_a_wake_record,test_an_agent_note_publishes_no_wake,test_a_note_is_saved_even_when_the_wake_cannot_be_published)Regression reproduction: extracted base commit ddc4b6c to a temp dir, overlaid the target commit's tests/fm-dashboard.test.sh, ran it; fails withnot ok - his note published no wake record for the card he wrote onManual E2E in a fresh temp FM_HOME with a real board started viabin/fm-dashboard.sh start:fm-dashboard.sh note <id> --tab communication --author admiralthen inspected$FM_HOME/state/.wake-queue(one 5-field check record, payloadcheck: dashboard-note <id> - he wrote on the card; read and answer it)Manual E2E: copied the queue into a scratch home and ranbin/fm-wake-drain.sh; it presented the dashboard-note record and printed WAKE_ACK_REQUIREDManual E2E:fm-dashboard.sh note ... --author agentleft the queue at exactly one dashboard-note recordManual E2E:chmod 000 $FM_HOME/state, POST /api/tasks/<id>/notes as admiral -> HTTP 201 with the note in the response;fm-dashboard.sh showlists the note; no new queue record;state/dashboard.logcontainsNOTE NOT ANNOUNCED;/api/audit/statuslog holds anote-wake-unpublishederror finding naming the cardHeadless Chrome screenshot of the running board page showing the discrepancy-log row for the unpublished note wake✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.