feat(bin): stamp status appends with UTC time and report state age - #1262
Open
tomharper wants to merge 3 commits into
Open
feat(bin): stamp status appends with UTC time and report state age#1262tomharper wants to merge 3 commits into
tomharper wants to merge 3 commits into
Conversation
5 tasks
A crewmate status line carried no time, so firstmate could not tell a fresh event from one that had sat in the log for hours: a `done:` line was relayed as current work after the worker had already stopped with the change unpushed, and repeated "shipshape" reports came from status lines whose underlying run had failed hours earlier. File mtime only ever dated the last line. - bin/fm-classify-lib.sh becomes the single owner of an ISO-8601 UTC stamp format, the copy-pasteable command that emits it, a firstmate-side writer helper, and the parsers that strip it. The verb, note, and decision-key parsers drop a leading stamp first so its own colons are never read as the verb separator. - bin/fm-brief.sh scaffolds the stamp into the ship, scout, and secondmate status instructions, still as one self-contained line that needs no helper on the worker's PATH. - bin/fm-crew-state.sh reports the age of the evidence behind its verdict: `live` for a run-step or pane read, the line's own elapsed time for a status-log read. The fleet snapshot exposes it as a field and the fleet view renders it. - Firstmate-side writers (spawn failure, captain-held transfer, pending-reply escalation, secondmate report helper) stamp too. Legacy untimestamped lines keep classifying exactly as before and report an unknown age rather than a guess; verified against the 26 real status logs in the primary home.
tomharper
force-pushed
the
fm/status-timestamps
branch
from
July 29, 2026 22:53
2962a95 to
8c81c17
Compare
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
Crewmate status lines carried no timestamp, so firstmate could not tell a fresh state from a stale one. Real failures on 2026-07-29: firstmate read a worker's 'done:' line and reported the task complete when that line was old and the work was actually unpushed with a cancelled pipeline; and repeated 'shipshape' reports came from status lines whose underlying run had failed hours earlier. File mtime only ever dated the LAST line of a status log, so every earlier event was undated.
The user asked for four things: (1) timestamp every status append emitted by the generated scaffolds in bin/fm-brief.sh (ship, scout, and secondmate charter), keeping the line parseable by the existing consumers bin/fm-classify-lib.sh and bin/fm-crew-state.sh by UPDATING those parsers rather than breaking them, and preferring a leading ISO-8601 UTC stamp; (2) keep the scaffolded status instruction ONE copy-pasteable line a worker can run, explicitly not a helper script the worker has to find; (3) make bin/fm-crew-state.sh report how old the state it returns actually is, so a 'done' from two hours ago and one from ten seconds ago do not look identical; (4) parse existing legacy untimestamped lines as 'age unknown' rather than crashing or guessing. Constraints: do not change the status file's sparseness contract or its wake behaviour, and bin/fm-brief.sh is fragile (an unescaped apostrophe in a heredoc broke it in issue #1069) so both ship and scout briefs must actually be generated and confirmed after editing.
Decisions made while implementing, which a reviewer reading only the diff would not know:
Pre-existing unrelated failures in this environment, confirmed identical on the unmodified baseline by stashing: tests/fm-pi-watch-extension.test.sh, tests/fm-bearings-snapshot.test.sh, and tests/fm-secondmate-harness.test.sh (the last resolves harness ancestry as 'claude' because the suite runs under a claude harness).
What Changed
bin/fm-classify-lib.shbecomes the single owner of a leading ISO-8601 UTC status stamp: it defines the format, the literal copy-pasteabledate -ucommand the brief scaffolds embed, anfm_status_stampwriter helper, and stamp/epoch/age primitives (epoch conversion is pure integer arithmetic rather thandate(1)).status_line_verb,status_line_note,_fm_decision_key, andstatus_is_captain_relevantnow strip a recognized stamp before parsing, so the stamp's own colons are not read as the verb separator; untimestamped legacy lines classify exactly as before and report an unknown age.bin/fm-brief.shemits the stamp inside each generated status instruction for the ship, scout, and secondmate scaffolds, keeping it one self-contained line a worker can copy. The four firstmate-side writers that also append status lines — spawn failure, decision-hold captain-held transfer, pending-reply escalation, andfm-secondmate-report.sh— stamp their appends too; the escalation dedup grep moves from whole-line to substring since the payload is no longer alone on the line.bin/fm-crew-state.shadds anagefield reporting how old the evidence behind its verdict is (livefor a run-step or pane read, elapsed time for a status-log line,unknownfor legacy or no source), placed aftersource:sofm-fleet-snapshot.sh's separator parsing keeps working; the snapshot exposesageas its own JSON field andfm-fleet-view.shrenders it in the Current column. Tests were added to the existingfm-brief,fm-watch-triage,fm-crew-state,fm-fleet-snapshot-view, andfm-pending-replysuites, all passing.Risk Assessment
✅ Low: The follow-up commit removes a hazardous writer-only override and corrects one stale header comment without touching any parsing or output path — the three briefs regenerate byte-identically — leaving a well-bounded, backward-compatible change whose legacy-line and stamp primitives are directly test-pinned.
Testing
I ran the five suites that own this change (fm-brief, fm-watch-triage, fm-crew-state, fm-fleet-snapshot-view, fm-pending-reply) plus the decision-hold, instruction-owners and supervision-instructions suites for the other stamped writers, and all passed. Because passing unit tests alone would not show the reported failure being fixed, I also built a scripted end-to-end walkthrough: it generates a real ship brief, has a simulated worker copy-paste and run the scaffolded status line verbatim, then reads the result through fm-crew-state.sh, fm-fleet-view.sh and fm-fleet-snapshot.sh, with the unmodified base commit run side by side. The transcript shows the original defect (an untimestamped
done:reads identically whether seconds or hours old) and the fix (age: 2h14mvsage: 7s,age: unknownfor a legacy line that still classifies,age: livefor pane/run-step reads, and age as its own fleet-view column and JSON field). This change is CLI/text-surface only with no rendered UI, so the reviewer-visible evidence is the CLI transcript rather than a screenshot.Evidence: End-to-end transcript: stale vs fresh vs legacy status age (before/after base commit)
=== 1. What the crewmate is actually handed (real generated ship brief) === 4. Report status by appending one line:echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) {state}: {one short line}" >> '.../state/status-age-demo.status'States: working, needs-decision, blocked, paused, done, failed. Keep the leading UTC stamp on every append: it is how firstmate tells a report you just wrote from one that has been sitting in the log for hours. === 2. The crewmate copies that ONE line and runs it verbatim === status log now: 2026-07-29T19:28:38Z working: reproduced the stale-done report === 3. BEFORE (base commit 99533c5): the incident - a stale done is unreadable as stale === status log (untimestamped, as crewmates used to write it): done: PR #1233 checks green base fm-crew-state.sh reports: state: done · source: status-log · PR #1233 checks green and that line reads exactly the same whether it was written 10 seconds or 2 hours ago - which is how an old done: was relayed as current work. === 4. AFTER: the same two dones, one 2h14m old and one 7s old === state: done · source: status-log · age: 2h14m · PR .../pull/1233 checks green state: done · source: status-log · age: 7s · PR .../pull/1233 checks green === 5. Legacy untimestamped status log: parsed as before, age unknown (never guessed) === working: started needs-decision: postgres or sqlite? state: parked · source: status-log · age: unknown · postgres or sqlite? === 6. Firstmate-side writers stamp too (secondmate report helper) === 2026-07-29T19:28:39Z done [corr=00112233445566aa]: delegated scope handled (via-helper) === 7. Where firstmate actually reads it: fleet view + snapshot JSON === | ID | Current (state / source / age) | Kind | Repo/Project | Backend | ... | legacy-task | working / status-log / unknown | scout | ... | status-age-demo | done / status-log / 2h14m | scout | ... {"id":"status-age-demo","current_state":{"state":"done","source":"status-log","age":"2h14m","detail":"PR .../pull/1233 checks green",...}}Evidence: Scaffolded status line is identical and single-line in the scout and secondmate briefs
scout brief: echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) {state}: {one short line}" >> '$FM_HOME/state/brief-scout.status' secondmate brief: echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) {state}: {one short line}" >> '$FM_HOME/state/brief-secondmate.status'Evidence: Reproducible demo script used to produce the transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed ✅
bin/fm-brief.sh:76-STATUS_STAMP_CMD=${FM_STATUS_STAMP_CMD:-$FM_STATUS_STAMP_CMD_DEFAULT}is a writer-side-only override with no parser counterpart, so it can silently defeat every parser this change relies on. UnlikeFM_CLASSIFY_PAUSED_VERB, which both the brief writer and the classifiers read,_FM_STATUS_STAMP_GLOBin bin/fm-classify-lib.sh is a hardcoded literal that cannot follow the override. Concrete failure: withFM_STATUS_STAMP_CMD='date -u'the scaffold emitsWed Jul 29 12:00:00 UTC 2026 done: shipped; the glob does not match,_fm_status_strip_stampleaves the line intact, andstatus_line_verbreturnsWed Jul 29 12. Every crewmate report from that home then loses its terminal verb:status_is_captain_relevantfalls through to the free-text regex,status_is_pausednever fires,status_open_decisionsfiles each event under a garbage key, andfm-crew-state.shmaps the line tounknown. The knob is undocumented (absent from docs/configuration.md) and unused by any test, so nothing catches the breakage. Either drop the override, or have fm-brief.sh validate the override's output against_FM_STATUS_STAMP_GLOBand fall back toFM_STATUS_STAMP_CMD_DEFAULTwhen it does not match.bin/fm-classify-lib.sh:434-crew_absorb_class's header still documents fm-crew-state.sh's canonical line as"state: <s> · source: <src> · <detail>", which no longer matches what the helper emits now thatage:sits between source and detail (bin/fm-crew-state.sh:107). The parsing code below it is correct —src=${line#*source: }; src=${src%% *}still yields the source token — so this is documentation drift only, but it is the sole remaining in-repo copy of the old format and this file is the declared owner of the status vocabulary.bin/fm-bearings-snapshot.sh:377- The bearings projection builds its in-flight rows as{id, kind, state, doing}and never reads the newcurrent_state.agefield the snapshot now exposes, so on the orient surface adonefrom two hours ago and one from ten seconds ago still render identically — the exact ambiguity behind the 2026-07-29 incident. This is not a regression (the age was never there) and it is outside the intent's stated scope, which named fm-crew-state.sh, the snapshot JSON, and fm-fleet-view.sh; the snapshot already carriesageas its own field, so surfacing it in bearings is a one-line follow-up rather than anything blocking here.bin/fm-supervise-daemon.sh:346- The daemon's.subsuper-seen-status-<task>dedup compares the stored marker against the current last status line verbatim. Because every append now carries a distinct stamp, two textually identical reports (e.g.blocked: waiting on reviewappended at 10:00 and again at 11:00) are no longer collapsed into one escalation the way they were before. Per-wake enqueueing is driven by file writes rather than content, so the wake contract the intent asked to preserve is unchanged, and re-surfacing a genuinely new event is the more correct behaviour — noting it only because it is a behavioural consequence of the stamp that the diff does not call out.🔧 Fix: drop unusable status stamp override, fix stale format comment
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-brief.test.sh(includes the new one-line stamped-scaffold test across ship/scout/secondmate)bash tests/fm-watch-triage.test.sh(includestest_status_timestamp_contract: stamp/epoch/age primitives, mixed stamped+legacy folds)bash tests/fm-crew-state.test.sh(includestest_reported_state_carries_evidence_age: old/fresh/legacy/live-pane/live-run-step/no-source)bash tests/fm-fleet-snapshot-view.test.sh(age as its own JSON field, age rendered in the fleet-view Current column)bash tests/fm-pending-reply.test.sh(stamped escalation append still classifies as blocked; substring dedup)bash tests/fm-decision-hold-lifecycle.test.sh,bash tests/fm-instruction-owners.test.sh,bash tests/fm-supervision-instructions.test.sh(other stamped writers + instruction ownership)Manual end-to-end: generated a real ship brief withbin/fm-brief.sh, ran the scaffolded status command verbatim as a worker would, then read it back viabin/fm-crew-state.sh,bin/fm-fleet-view.sh,bin/fm-fleet-snapshot.shManual before/after: extracted base commit 99533c5 withgit archiveand ran itsbin/fm-crew-state.shon the same status log to show the stale-done:ambiguity the change removesManual:bin/fm-secondmate-report.sh <log> done <corr> <note>to confirm firstmate-side writers also stampManual: captured the scaffolded status line from the scout and secondmate briefs to confirm one identical copy-pasteable line per scaffold.agents/skills/firstmate-codexapp/SKILL.md:64- The Codex Desktop status instruction in .agents/skills/firstmate-codexapp/SKILL.md hand-copies the literal stamp formatdate -u +%Y-%m-%dT%H:%M:%SZ, which bin/fm-classify-lib.sh owns as FM_STATUS_STAMP_FORMAT / FM_STATUS_STAMP_CMD_DEFAULT. Unlike bin/fm-brief.sh, which interpolates the owner's variable, this surface is prose in a skill and cannot interpolate, so the two copies can silently drift if the format ever changes. Left as-is: it is the necessary form for a prompt a human pastes into Desktop, and closing the drift risk properly means a test or lint assertion (executable change), which is out of scope for the documentation phase. Follow-up worth considering: pin the SKILL.md literal against FM_STATUS_STAMP_CMD_DEFAULT in tests/fm-watch-triage.test.sh.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.