This repository was archived by the owner on Aug 25, 2026. It is now read-only.
feat(herdr): add readable task tab labels - #76
Merged
Merged
Conversation
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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
Ship Herdr-only display tab labels in the accepted Option C shape ' - · '. Keep workspace identity as firstmate / 2ndmate- and do not change tmux naming. Map ship to Crew, scout to Scout, and secondmate agent to 2nd; derive a deterministic 1-28 character phrase from explicit display title, backlog title, then semantic id fallback; use a stable 4-6 character id-tail or SHA key and extend on collision; cap the full label at 50 characters and enforce the report's safe charset. Set the label once at spawn, persist display_label plus full machine identity and exact Herdr tab/pane ids, journal before create for crash recovery, prefer exact ids then display_label while retaining legacy fm- discovery, and add deterministic intake plumbing, documentation, and hermetic tests. Do not introduce pretty-label-only recovery, LLM titles, phase renames, workspace identity changes, or tmux naming changes.
What Changed
<kind> - <phrase> · <task-key>format, with validated title sources, safe character and length limits, and collision-resistant keys.fm-<id>tabs while leaving tmux naming unchanged.Risk Assessment
✅ Low: Captain, the documentation fix now aligns the authoritative operating contract with the implemented Herdr presentation and machine-identity split, and no material regressions remain.
Testing
The supplied full behavior baseline and focused label, intake, spawn, adapter, recovery, secondmate-routing, and unchanged-tmux tests passed. A hermetic end-to-end spawn produced a rendered screenshot plus raw label, metadata, journal, and CLI evidence; a live Herdr screenshot was intentionally avoided because mutating the captain’s real session was outside this gate-worktree validation boundary.
/tmp/no-mistakes-evidence/01KY8DMPEWVRF5TZG4ZKWFR3WK/herdr-tab-label-evidence.png)Evidence: Interactive evidence source
Evidence: Hermetic Herdr CLI transcript
Evidence: Persisted task metadata
Evidence: Pre-create recovery journal
Evidence: Visible tab label
Scout - Herdr labels · c1dbPipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
🔧 **Rebase** - 10 issues found → auto-fixed ✅
.agents/skills/fmx-respond/SKILL.md- merge conflict rebasing onto origin/mainAGENTS.md- merge conflict rebasing onto origin/mainREADME.md- merge conflict rebasing onto origin/mainbin/fm-x-followup.sh- merge conflict rebasing onto origin/mainbin/fm-x-lib.sh- merge conflict rebasing onto origin/mainbin/fm-x-reply.sh- merge conflict rebasing onto origin/maindocs/architecture.md- merge conflict rebasing onto origin/maindocs/configuration.md- merge conflict rebasing onto origin/maindocs/scripts.md- merge conflict rebasing onto origin/maintests/fm-x-mode.test.sh- merge conflict rebasing onto origin/main🔧 Fix applied.
✅ Re-checked - no issues remain.
🔧 **Review** - 6 issues found → auto-fixed (10) ✅
bin/backends/herdr.sh:921- Required recovery is not operational: the criterion says “prefer exact ids then display_label while retaining legacy fm-<id> discovery,” but this new helper has no production caller or generic backend wrapper. Its implementation also maps readable tabs by label at line 934 without first comparing persisted session/workspace/tab/pane IDs. Wire recovery into the lifecycle and resolve exact IDs before unique label fallback.bin/fm-task-label-lib.sh:140- The required safe charset is not enforced when reusing persisted records. The*phrase glob accepts empty phrases,/,:, quotes, and arbitrary Unicode, contradicting “enforce the report's safe charset.” Validate the 1–28 character phrase against the same ASCII grammar used during generation.bin/fm-task-label-lib.sh:222- The required collision extension is racy. Collision scanning occurs before journal publication and before Herdr’s workspace lock, so concurrent spawns can both reserve the same short key; identical labels may also cause one new pane to be mistaken for a replaceable husk. Serialize collision selection, journal reservation, and creation.bin/backends/herdr.sh:923- Recovery converts workspace and tab-list failures into a successful empty inventory and skips pane lookup failures. A transient Herdr or JSON error therefore looks like no live tasks. Propagate these failures and validate responses before emitting inventory.bin/fm-task-label-lib.sh:232- The 50-character check is locale-dependent: underLC_ALL=C, the UTF-8 middot counts as three bytes, so a valid 49-character Scout label with a 28-character phrase and extended key is rejected as 51 bytes. Use an explicit character-counting rule and cover C and UTF-8 locales.bin/fm-brief.sh:74- The display-title record is written before task-shape and project validation. A failed brief command can leave durable intake without a brief, and a later spawn may consume that stale title. Publish it only after validation or clean it up on failure.🔧 Fix: Harden Herdr label recovery and spawn serialization
6 errors still open:
bin/fm-backend.sh:199- Journal-only crash recovery returns a Herdr target, but no.metarecord exists yet, sofm_backend_of_selectorlater defaults that target to tmux. This contradicts “journal before create for crash recovery” becausefm-sendandfm-peekwill operate through the wrong backend. Preserve or return the recovered backend alongside the target.bin/backends/herdr.sh:997- Display-label recovery only recognizes labels with exactly four key characters. Valid 5–6 character keys and collision-extended keys are excluded, contradicting “use a stable 4-6 character id-tail or SHA key and extend on collision” and “prefer exact ids then display_label.” Reuse the full label-record validator instead of this fixed-width pattern.bin/fm-spawn.sh:880- Secondmate creation selects its Herdr workspace fromPROJ_ABSbut writes the recovery journal into the primary$STATE; the journal does not retain the secondmate home/workspace needed to inventory that workspace after a pre-meta crash. This leaves the required crash recovery unavailable for secondmate tasks. Persist and use the workspace identity during recovery.bin/backends/herdr.sh:670- Collision reservation is protected by a workspace-specific lock while primary and secondmate spawns scan and update the same primary label state. Concurrent spawns in different Herdr workspaces can therefore reserve the same short key despite the required collision extension. Use one lock for the shared reservation state or separate the state by workspace.tests/fm-backend-herdr.test.sh:707- The concurrency test relies on a 50 ms sleep to assume the first process acquired the lock. A delayed scheduler can let the second process win and reverse the expected keys, contradicting the requirement for hermetic tests. Replace the timing assumption with an explicit barrier indicating lock acquisition.tests/fm-task-label-lib.test.sh:127- The character-count test fails when none of four hardcoded UTF-8 locale names is installed, even though other valid UTF-8 locales may exist. This makes the required hermetic test suite depend on host locale provisioning; create a locale-independent assertion or skip only the locale-specific comparison.🔧 Fix: Harden Herdr crash recovery and label reservations
2 errors still open:
bin/fm-backend.sh:201- The criterion requires “journal before create for crash recovery” and display-label fallback, but secondmate recovery overridesFM_HOMEwithout retaining the primary$stateasFM_STATE_OVERRIDE. Label correlation consequently searches<secondmate-home>/stateinstead of the directory containing the journal, so journal-only secondmate selectors still cannot recover. PassFM_STATE_OVERRIDE="$state"into the inventory call.bin/fm-fleet-snapshot.sh:51- The criterion requires deriving the phrase from the backlog title, but this regex omits the closing\)and never strips normal metadata suffixes such as(repo: demo, kind: ship). Short backlog titles therefore produce labels containing routing metadata. Correct the suffix regex and cover a short title that does not hide the leak through truncation.🔧 Fix: Fix secondmate recovery and backlog title parsing
5 errors still open:
bin/fm-backend.sh:182- The required “prefer exact ids then display_label” recovery is still incomplete: an existing Herdr meta record returns its persisted pane immediately, without checking that endpoint or falling back through its persisted label.fm-sendandfm-peektherefore fail instead of recovering when exact IDs are stale. Route failed exact-ID resolution through the safe label inventory.bin/fm-backend.sh:209- The required “retaining legacy fm-<id> discovery” is not wired into production resolution. With no meta or journal, a canonicalfm-<id>selector errors before Herdr inventory is queried, so a surviving legacy-labeled tab cannot be discovered. Add a Herdr legacy-inventory fallback before this error.bin/fm-backend.sh:175- The new raw-meta lookup runs before documentedfm-<id>interpretation. Because valid task IDs may begin withfm-,fm-send fm-foocan selectstate/fm-foo.metainstead of the canonicalstate/foo.meta, potentially sending to the wrong task and contradicting retained legacy selector behavior. Give prefixed selectors their established stripped-ID meaning and reserve exact lookup for bare inputs.bin/fm-fleet-snapshot.sh:44- The required backlog-title fallback handles only checkbox rows with trailing metadata. It misses the repository-supported bold in-flight form- **<id>** - ..., and its end-anchored suffix removal leaves metadata in canonical queued rows followed byblocked-by:. Parse every supported backlog form and isolate the title before routing metadata.tests/fm-task-label-lib.test.sh:46- The required hermetic tests directly requiresha256sum, although production intentionally falls back toshasum. Hosts providing only the supported fallback fail this test for environmental reasons. Compare against a fixed digest or call the production hash helper.🔧 Fix: Complete Herdr recovery and backlog title parsing
2 errors still open:
bin/fm-backend.sh:216- The required order “prefer exact ids then display_label while retaining legacy fm-<id> discovery” is still collapsed into onefm-$idalias. Inventory emits that alias for both a persisted display-label match and a raw legacy tab, so stale-ID recovery can select an old legacy pane or fail as ambiguous instead of preferring the recorded display label. Match the persisted label distinctly and use legacy discovery only afterward.bin/fm-fleet-snapshot.sh:53- The required backlog-title fallback still includes completion metadata for supported Done rows. The parser accepts- [x] <id> - ...but strips only repository andblocked-by:suffixes, soFix UI - local main (merged ...)becomes the phrase instead ofFix UI. Parse the PR, local-main, and report completion suffixes and cover short Done rows.🔧 Fix: Prioritize label recovery and clean Done titles
1 error still open:
bin/backends/herdr.sh:1005- The tab-delimited inventory is injectable becausejq -remits raw labels andfm-*labels bypass validation. A label containing tabs, such asfm-victim<TAB>fm-victim, can satisfy the legacy matcher and route sends or peeks to the wrong pane. Encode inventory structurally or validate the complete legacy label against the canonical safefm-<id>grammar before recovery.🔧 Fix: Validate legacy Herdr recovery labels
1 error still open:
bin/backends/herdr.sh:1005- Recovery remains record-separator injectable:jq -rdecodes newlines before validation, so a label likejunk\nw1:t4\tfm-victimbecomes a forged valid inventory row and can route recovery to the wrong pane. Validate each complete JSON label before serialization or use structural encoding, then add a newline-injection regression alongside the tab case.🔧 Fix: Block newline-forged Herdr recovery rows
2 issues (1 error, 1 warning) still open:
bin/backends/herdr.sh:1008- Rawjq -rextraction still enters Bash command substitution before validation. Bash drops NUL bytes, sofm-vic\u0000timbecomesfm-victim, passes legacy validation, and can forge recovery routing. Reject controls while values remain JSON-encoded or preserve an encoded representation through validation; add a NUL regression.bin/backends/herdr.sh:1006- A valid emptytabsarray producesrows='', but the here-string executes the loop once with an empty row and fails the sentinel check. Empty workspaces are therefore reported as inventory failures. Return success before the loop whenrowsis empty, or avoid the synthetic here-string row.🔧 Fix: Harden encoded Herdr inventory parsing
1 warning still open:
tests/fm-backend-herdr.test.sh:698- The empty-tabs regression checks only empty output. Because the suite usesset -u, a nonzero command that emits nothing still passes, so the test does not catch the exact failure it targets. Guard the assignment with|| failor explicitly assert a zero status.🔧 Fix: Assert empty Herdr inventory success
1 error still open:
AGENTS.md:127- The criterion requires “Ship Herdr-only display tab labels” and “add ... documentation,” and the changed spawn hunk now callsfm_backend_create_labeled_task; however, this authoritative contract still says Herdr uses anfm-<id>tab. Update it—and clarifydocs/operating-map.md:48—to describe the Option C label while retaining exact machine identity and unchanged tmux naming.🔧 Fix: Document Herdr display and machine identity
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash bin/fm-run-behavior-tests.shPre-supplied baseline:bash bin/fm-run-behavior-tests.sh(reported successful)bash tests/fm-task-label-lib.test.shbash tests/fm-brief-display-title.test.shbash tests/fm-backend-herdr.test.shEvidence-copy run:env -u NO_MISTAKES_GATE bash tests/fm-spawn-herdr-label.test.shEvidence-copy run:env -u NO_MISTAKES_GATE bash tests/fm-backend.test.shEvidence-copy run:env -u NO_MISTAKES_GATE bash tests/fm-send-herdr-secondmate-marker.test.shEvidence-copy run:env -u NO_MISTAKES_GATE bash tests/fm-spawn-route.test.shRenderedherdr-tab-label-evidence.htmland capturedherdr-tab-label-evidence.pngwithagent-browserVisually inspected the captured screenshot and verified the final worktree withgit status --porcelain=v1✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Captain, fix ShellCheck local variable declaration
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.