fix(composer): handle ANSI ghost text safely#429
Merged
Conversation
…uecolor Away-mode injection wedged all night on the primary claude-on-herdr pane: the herdr composer classifier never stripped generic dim ghost text (only a narrow codex bold-wrapped byte-pattern check), so claude's rotating prompt-suggestion ghost - a bare "❯" then SGR-2 dim text, which herdr's ANSI pane read preserves - read as real pending input and every escalation deferred (6524 lifetime "pending input (non-empty composer)" defers; wedge 30623s). Consolidate ghost extraction into one fleet-wide ANSI-aware owner, fm_composer_strip_ghost (bin/fm-composer-lib.sh), that drops every de-emphasised run - dim/faint (SGR 2: claude, codex) AND a dark/muted truecolor foreground (grok's placeholder, luminance below FM_COMPOSER_GHOST_LUMA_MAX, default 128, dark-theme assumption). Both ANSI-capable backends route through it: fm_tmux_composer_state (fm_tmux_strip_ghost is now a thin adapter) and fm_backend_herdr_composer_state. The herdr-only faint byte-pattern check is removed and fm_backend_herdr_strip_ansi reduced to a thin adapter over the shared fm_composer_strip_ansi. Bordered detection now reads the plain row so a dark box border dropped with the ghost does not lose the composer shape. This also closes the documented grok TRUECOLOR placeholder gap by the same mechanism (harness-adapters skill note updated). Empirical evidence (read-only live capture + isolated tmux, no herdr lifecycle) and the incident write-up are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh). Two prior ghost-test fixtures that used a near-black 38;2;1;2;3 as "real" colored text (never a realistic real-input color) are corrected to a bright 38;2;224;222;244, preserving the truecolor payload-skip parser intent.
Keigyoku
pushed a commit
to Keigyoku/firstmate
that referenced
this pull request
Jul 11, 2026
* fix(composer): one ANSI-aware ghost owner covers claude dim + grok truecolor Away-mode injection wedged all night on the primary claude-on-herdr pane: the herdr composer classifier never stripped generic dim ghost text (only a narrow codex bold-wrapped byte-pattern check), so claude's rotating prompt-suggestion ghost - a bare "❯" then SGR-2 dim text, which herdr's ANSI pane read preserves - read as real pending input and every escalation deferred (6524 lifetime "pending input (non-empty composer)" defers; wedge 30623s). Consolidate ghost extraction into one fleet-wide ANSI-aware owner, fm_composer_strip_ghost (bin/fm-composer-lib.sh), that drops every de-emphasised run - dim/faint (SGR 2: claude, codex) AND a dark/muted truecolor foreground (grok's placeholder, luminance below FM_COMPOSER_GHOST_LUMA_MAX, default 128, dark-theme assumption). Both ANSI-capable backends route through it: fm_tmux_composer_state (fm_tmux_strip_ghost is now a thin adapter) and fm_backend_herdr_composer_state. The herdr-only faint byte-pattern check is removed and fm_backend_herdr_strip_ansi reduced to a thin adapter over the shared fm_composer_strip_ansi. Bordered detection now reads the plain row so a dark box border dropped with the ghost does not lose the composer shape. This also closes the documented grok TRUECOLOR placeholder gap by the same mechanism (harness-adapters skill note updated). Empirical evidence (read-only live capture + isolated tmux, no herdr lifecycle) and the incident write-up are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh). Two prior ghost-test fixtures that used a near-black 38;2;1;2;3 as "real" colored text (never a realistic real-input color) are corrected to a bright 38;2;224;222;244, preserving the truecolor payload-skip parser intent. * no-mistakes(review): Preserve dark shell prompt safety * no-mistakes(review): Harden erased shell prompt classification * no-mistakes(document): Document shared composer ghost extraction * no-mistakes(lint): Normalize tmux comment punctuation (cherry picked from commit 0eaf293)
Keigyoku
pushed a commit
to Keigyoku/firstmate
that referenced
this pull request
Jul 11, 2026
* fix(composer): one ANSI-aware ghost owner covers claude dim + grok truecolor Away-mode injection wedged all night on the primary claude-on-herdr pane: the herdr composer classifier never stripped generic dim ghost text (only a narrow codex bold-wrapped byte-pattern check), so claude's rotating prompt-suggestion ghost - a bare "❯" then SGR-2 dim text, which herdr's ANSI pane read preserves - read as real pending input and every escalation deferred (6524 lifetime "pending input (non-empty composer)" defers; wedge 30623s). Consolidate ghost extraction into one fleet-wide ANSI-aware owner, fm_composer_strip_ghost (bin/fm-composer-lib.sh), that drops every de-emphasised run - dim/faint (SGR 2: claude, codex) AND a dark/muted truecolor foreground (grok's placeholder, luminance below FM_COMPOSER_GHOST_LUMA_MAX, default 128, dark-theme assumption). Both ANSI-capable backends route through it: fm_tmux_composer_state (fm_tmux_strip_ghost is now a thin adapter) and fm_backend_herdr_composer_state. The herdr-only faint byte-pattern check is removed and fm_backend_herdr_strip_ansi reduced to a thin adapter over the shared fm_composer_strip_ansi. Bordered detection now reads the plain row so a dark box border dropped with the ghost does not lose the composer shape. This also closes the documented grok TRUECOLOR placeholder gap by the same mechanism (harness-adapters skill note updated). Empirical evidence (read-only live capture + isolated tmux, no herdr lifecycle) and the incident write-up are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh). Two prior ghost-test fixtures that used a near-black 38;2;1;2;3 as "real" colored text (never a realistic real-input color) are corrected to a bright 38;2;224;222;244, preserving the truecolor payload-skip parser intent. * no-mistakes(review): Preserve dark shell prompt safety * no-mistakes(review): Harden erased shell prompt classification * no-mistakes(document): Document shared composer ghost extraction * no-mistakes(lint): Normalize tmux comment punctuation (cherry picked from commit 0eaf293)
Keigyoku
added a commit
to Keigyoku/firstmate
that referenced
this pull request
Jul 11, 2026
…, kunchenguid#435 deferred) (#8) * feat: add harness-aware supervision (kunchenguid#367) * Add harness-aware supervision * no-mistakes(review): Captain, harden watcher supervision regressions * no-mistakes(review): Captain, harden watcher supervision cadence * no-mistakes(review): Harden watcher supervision ownership * no-mistakes(review): Captain, harden Pi extension marker * no-mistakes(review): Captain, harden Pi supervision restart checks * no-mistakes(review): Harden watcher ownership checks * no-mistakes(review): Captain, harden Pi supervision loading * no-mistakes(review): Captain, require Pi guard extension loading * no-mistakes(review): Captain, harden watcher supervision recovery * no-mistakes(test): Fix fm-send baseline log filtering * no-mistakes(document): Sync harness supervision docs * no-mistakes: apply CI fixes (cherry picked from commit 090483e) * fix: split X-mode replies by platform (kunchenguid#369) * fix: make x replies split by platform * no-mistakes(review): Captain: preserve Discord recovery relink context * no-mistakes(test): Captain: keep split markers outside fences * no-mistakes(document): Sync X-mode reply docs (cherry picked from commit 4790bc5) * fix: make stow memory writes inspect before update (kunchenguid#372) * docs: make stow inspect-then-update * no-mistakes(review): Remove unsupported archive-body guidance * no-mistakes(review): Clarify stow read-before-write exception * no-mistakes(test): Require archive-body for stow task notes * no-mistakes(document): Sync stow memory docs * no-mistakes(lint): Silence ShellCheck source warning (cherry picked from commit af5361e) * fix(watcher): wait when arm attaches to a healthy watcher (kunchenguid#375) * fix: attach-and-wait when arm finds a healthy watcher Grok and Claude re-arm after every turn with work in flight. When a watcher was already healthy, fm-watch-arm exited immediately with watcher: healthy, which completed the harness background task and injected an empty false wake. Attach to the live identity-matched holder instead, stay until that cycle ends, then exit 0 so notify fires for a real end-of-cycle. The peer-startup-race path uses the same contract. --restart and the started path are unchanged. * no-mistakes(review): Gate restart watcher peer attach * no-mistakes(document): Sync watcher arm docs (cherry picked from commit 7302a95) * feat(pi): simplify primary session launch (kunchenguid#386) * docs(readme): reformat Quick Start and recommend Grok equally with Claude Code * no-mistakes(review): Captain: align harness launch guidance * no-mistakes(review): Captain, clarify Pi supervised launch * no-mistakes(review): Captain, document Pi first-launch bridge * feat(pi): track primary watcher extension for plain-pi launch Move Pi's primary watcher bridge from a generated state/ file to a tracked .pi/extensions/fm-primary-pi-watch.ts, matching how the turn-end guard extension already works: self-hashing version, project-local auto-discovery after one-time Pi trust. This drops the state/-generation step and dual -e requirement from the happy path, so Pi's Quick Start launch becomes plain 'pi', the same friction class as 'claude' and 'grok --trust'. - bin/fm-pi-watch-extension.sh is removed; nothing generates the extension anymore since it is committed. - fm-session-start.sh and fm-supervision-instructions.sh resolve the watcher extension path from FM_ROOT instead of state/, and the session-start diagnostic now points at restarting plain pi after trust, with -e as a documented fallback. - fm-spawn.sh points Pi secondmate launches at the tracked extension path in the secondmate home instead of generating a state/ copy. - README Quick Start Pi block is now just 'pi' plus a trust note. - Tests, docs, and the harness-adapters skill updated to match. * fix(pi): drop backticks from session-start diagnostic to satisfy shellcheck SC2016 (cherry picked from commit 1b5a9f5) * feat(supervision): prevent unsafe watcher-arm commands (kunchenguid#387) * feat(supervision): add PreToolUse seatbelt against watcher-arm anti-patterns Adds bin/fm-arm-pretool-check.sh, a shared PreToolUse-style checker that denies a primary shell command backgrounding, piping, or bundling the watcher arm/checkpoint, or force-killing the watcher process broadly - the exact shapes that silently took Grok's supervision down. Wires it into all five verified harnesses (grok, claude, codex, opencode, pi), each validated empirically against the real harness. Also fixes a grok 0.2.93 regression discovered during that validation: the existing turnend-guard Stop hook's bare root variable broke grok's own variable pre-substitution and silently no-op'd the hook. * no-mistakes(review): Harden watcher arm validation * no-mistakes(review): Harden arm guard metacharacter checks * no-mistakes(review): Harden nested shell arm guard * fix(lint): rewrite SC2015 guards in fm-arm-pretool-check.sh as if/then A && B || C is not if-then-else; C can run when A is true. Replace both occurrences of the quote-state early-continue with an explicit if/then. (cherry picked from commit 766f772) * fix(pi): restore primary watcher supervision lifecycle (kunchenguid#397) * fix Pi primary supervision lifecycle * no-mistakes(document): Synchronize Pi primary extension documentation (cherry picked from commit 80ecc8d) * fix: keep persistent secondmates out of the main backlog (kunchenguid#398) * fix secondmate backlog guidance * no-mistakes(review): Require reasons for captain backlog holds * no-mistakes(test): Document secondmate handoff skill requirement * fix secondmate teardown reminder * no-mistakes(document): sync teardown reminder docs to work-items-only backlog contract (cherry picked from commit 7525a37) * fix(backlog-handoff): move full item blocks including indented bodies (kunchenguid#401) * fix(backlog-handoff): move full item blocks including indented bodies fm-backlog-handoff only moved the checklist header line, so multi-line item bodies were left orphaned in the source backlog and never reached the secondmate. Move the full block (header plus indented body lines) atomically, treating body membership by indentation so lines like ## Intent stay with the item, and add regression coverage. * no-mistakes(review): Captain: preserve EOF handoff terminators * no-mistakes(review): treat blank lines inside item bodies as movable body * no-mistakes(document): sync backlog-handoff docs with full-block move behavior (cherry picked from commit 600075e) * feat(herdr): make Herdr lab lifecycle safety deterministic for briefs (kunchenguid#402) * guard Herdr lab lifecycle in briefs * no-mistakes(review): Fix Herdr lab helper and provisioning safety * no-mistakes(review): Captain, harden Herdr lab lifecycle safety * no-mistakes(review): fix Herdr lab test cleanup ordering and brief help range * no-mistakes(review): reject leading options in Herdr lab run guard * no-mistakes(review): strip leading non-alnum in Herdr lab name generator * no-mistakes(document): document Herdr lab helper and --herdr-lab brief flag * no-mistakes(lint): add shellcheck disable for deliberate SC2016 literals in fm-brief herdr-lab * no-mistakes: apply CI fixes (cherry picked from commit 4bc0824) * fix(watcher): classify arm-command seatbelt by execution position (kunchenguid#403) * fix watcher arm command policy * no-mistakes(review): Harden watcher command policy parsing * no-mistakes(review): Captain: harden watcher policy parsing * no-mistakes(review): harden watcher policy for expanded paths, direct-watch, and sound prefilter * no-mistakes(review): close prefilter and classifier locale/ANSI-C watcher-path decode gaps * no-mistakes(review): fail closed on loop-wrapped broad watcher kills * no-mistakes(document): sync docs for watcher-arm command-position policy (cherry picked from commit 22b1d71) * fix: reconcile existing AGENTS.md safely (kunchenguid#405) * fix(agents-md): inject self-governance section into existing AGENTS.md fm-ensure-agents-md.sh only appended the canonical "## Maintaining this file" section on skeleton create or CLAUDE.md promotion, so an existing AGENTS.md that lacked it exited unchanged and forced hand-copying the wording during a rollout across existing projects. Call the already- idempotent ensure_maintenance_section on the existing-AGENTS.md paths and report whether the file changed; a re-run and an already-complete file stay byte-identical. Also fixes kunchenguid#389: refuse a case-variant real memory file (e.g. a lowercase agents.md) instead of silently emitting a CLAUDE.md symlink whose uppercase literal target dangles once the tree lands on a case-sensitive filesystem. Tests extend tests/fm-ensure-agents-md.test.sh; skeleton-create and CLAUDE.md-promotion regressions still pass. Docs updated to match. * no-mistakes(review): Captain: preserve CRLF maintenance-section idempotency * no-mistakes(review): Preserve CRLF during maintenance-section injection * no-mistakes(review): Captain: harden dangling-symlink regression coverage * no-mistakes(document): Document agent-memory injection outcomes (cherry picked from commit 96244f4) * feat: support project-less secondmate homes (kunchenguid#409) * feat(secondmate): support project-less homes via --no-projects fm-brief.sh --secondmate and fm-home-seed.sh now accept an explicit --no-projects signal to scaffold, seed, and register a secondmate home whose subject is the firstmate repo itself (no clones). The signal is mutually exclusive with a project list; omitting both still fails loudly so an accidental omission is never a silent project-less seed. The registry line renders an empty projects: field, which spawn and the snapshot already tolerate. Docs updated in the secondmate-provisioning skill and both script headers. * no-mistakes(review): Captain: document project-less secondmate flow * no-mistakes(review): Captain: refuse project-less reseeding of populated homes * fix(seed): fail closed on unreadable project data * no-mistakes(review): Captain: reject stale projectful charters * no-mistakes(review): Captain: fail closed on unsafe project paths * no-mistakes(review): Captain: validate project-less charter clone sections * no-mistakes(document): Document project-less secondmate seeding (cherry picked from commit 08453f9) * fix: delegate backlog handoffs to tasks-axi (kunchenguid#411) * wip(handoff): record verified delegation design + tasks-axi mv blocker No production code changed yet. tasks-axi mv (v0.2.1) cannot atomically move a blocked-by-linked item set across backlogs (deadlocks both orders, no batch/--force), which fm-secondmate-lifecycle-e2e requires. Parked pending a tasks-axi connected-set mv enhancement; note captures the verified design, semantics, test/CI/doc changes, and resume checklist. * refactor(handoff): delegate the item move to tasks-axi mv fm-backlog-handoff.sh's two-pass awk was a second parser of the backlog format and the source of the PR kunchenguid#401 body-orphaning drift. Delete it and delegate the move to `tasks-axi mv <id>... --to <dest>` (v0.2.2 atomic multi-id), the single owner of the format: a connected set (blocker plus dependents) moves together with blocked-by preserved, item blocks stay byte-exact, and destination section placement holds. The helper keeps only the fleet-level validation tasks-axi cannot know - secondmate-home resolution, the seeded-home safety checks, the In-flight refusal, and idempotent per-key reporting - and is atomic: on any move failure nothing moves. Tests: fm-backlog-handoff.test.sh keeps PR kunchenguid#401's regression matrix but now exercises the delegated path and skips cleanly when tasks-axi is absent; the two whole-file fixtures move to tasks-axi's canonical whitespace. The lifecycle-e2e and safety move-cases gain the same skip guard. CI installs tasks-axi so the delegated path is exercised. Docs state that config/backlog-backend=manual governs firstmate's own hand-editing, not this validated helper, which delegates fleet-wide because bootstrap requires tasks-axi on PATH. Remove the now-redundant WIP design note. * no-mistakes(review): Captain: harden atomic backlog handoffs * no-mistakes(review): Captain: enforce queued-only backlog handoffs * no-mistakes(review): Captain: harden handoff section parsing * no-mistakes(document): Document delegated backlog handoffs * no-mistakes(lint): Silence ShellCheck source diagnostics (cherry picked from commit 31afb8c) * fix: ignore secondmate home marker during sync (kunchenguid#417) * fix: gitignore the secondmate home marker bin/fm-home-seed.sh writes an untracked .fm-secondmate-home marker into every seeded secondmate home. A secondmate home is a worktree of the firstmate repo, so any plain `git status --porcelain` dirtiness check counted the untracked marker and the home read as dirty forever: fleet-sync reported it STUCK and the local fast-forward convergence sweeps risked leaving it stale on firstmate updates. Add .fm-secondmate-home to the tracked .gitignore so the marker is invisible to every dirtiness check uniformly, without weakening fleet-sync's deliberate untracked-counting for project clones. Convergence chicken-and-egg: existing homes predate the fix and it only arrives by fast-forward. The already-present marker-tolerant ff-skip (ignore_seed_marker=yes, used by the bootstrap sweep, /updatefirstmate, and spawn pre-launch) advances such a home past the fix commit, after which .gitignore takes over - no hand intervention. Tests in tests/fm-secondmate-sync.test.sh cover a freshly seeded home reading clean, an existing marker-only home converging then reading clean, and a genuinely dirty home still skipping. * no-mistakes(review): Captain: document standalone-clone update path * no-mistakes(document): Document secondmate marker migration (cherry picked from commit 171207c) * fix(composer): prevent dead-shell message injection (kunchenguid#416) * fix(composer): stop reading dead-shell prompts as empty agent composers Consolidate composer empty/pending/unknown classification into one shared owner, bin/fm-composer-lib.sh's fm_composer_classify_content, delegated to by all four backend adapters (tmux via fm-tmux-lib.sh, herdr, orca, cmux). This replaces four drifting copies of the glyph decision. Safety fix: a bare shell prompt glyph (> $ % #) on an unstructured row is now classified unknown (a dead shell, unsafe for injection), not empty. It is only empty inside a bordered composer box (the harness's own prompt). Agent glyphs ❯ (claude) and › (codex) read empty either way. The away-mode injector (inject_msg) now requires an affirmatively-empty composer, deferring on pending or unknown, so an escalation can never be typed into (or executed by) a pane whose agent exited to its login shell. Regression coverage: new tests/fm-composer-lib.test.sh pins the shared owner; per-backend dead-shell tests in fm-daemon (tmux + injector), orca, and the existing herdr/cmux suites. shellcheck clean; herdr incident regressions stay green. * no-mistakes(review): Captain: harden composer safety checks * no-mistakes(test): Stabilize Herdr prune safety setup * no-mistakes(document): Document composer injection safety * no-mistakes(lint): Clean composer safety lint * no-mistakes: apply CI fixes (cherry picked from commit a955a05) * feat(watcher): add paused external-wait supervision (kunchenguid#421) * feat(watcher): add paused/awaiting-external crew state A crew (or firstmate steering it) can declare a deliberate wait on a known external dependency with a paused: <reason> status. Both the always-on watcher and the away-mode daemon absorb such an idle pane through shared fm-classify-lib.sh vocabulary instead of tripping the possible-wedge stale escalation, and re-surface it for a recheck only on a long bounded cadence (FM_PAUSE_RESURFACE_SECS) so a forgotten pause cannot rot invisibly. fm-crew-state.sh reports state: paused distinctly. A crew that goes idle without declaring a pause classifies exactly as before. Docs and brief scaffold state lists updated; tests colocated. * no-mistakes(review): Captain: fix paused-state transitions * init * no-mistakes(review): Captain: fix paused-state supervision transitions * no-mistakes(review): Captain: fix paused supervision handoffs * no-mistakes(review): Reconcile paused supervision markers * no-mistakes(review): Captain: prioritize paused states over captain relevance * no-mistakes(review): Captain: preserve paused-working wedge timer * no-mistakes(review): Captain: honor configured pause verb in briefs * no-mistakes(test): Captain: fix AFK paused watcher handoff * no-mistakes(document): Document declared external waits * no-mistakes(lint): Clean paused-state lint --------- Co-authored-by: fmtest <fmtest@example.invalid> (cherry picked from commit 7788fa3) * fix: preserve X-mode follow-up platform limits (kunchenguid#425) * fix(x-mode): make follow-up platform splitting immune to link ordering A ~470-char Discord follow-up posted as a (1/2)(2/2) thread split at ~280 chars because fm-x-link only learned the platform from the inbox payload, and the fmx-respond ack path can drain that inbox file before the task is linked. A link recorded after cleanup silently lost the platform and the splitter defaulted to the X 280-char budget. Make platform resolution ordering-proof: - fm-x-link now resolves the platform AUTHORITATIVELY by request_id via a new fmx_request_relay_context helper (POST /connector/request-context) when neither the inbox payload nor carry flags carry it. The request_id survives the inbox drain, so a post-cleanup link still learns the right split budget. Best-effort: no token/curl or a non-2xx relay degrades to the loud warning below rather than a silent X default. - fm-x-link warns loudly when no platform source resolves, so the loss is never silent. - The fmx-respond procedure now orders link-before-inbox-cleanup so the fast local path stays correct without a relay round-trip. Colocated regression tests: a Discord follow-up >280 <2000 posts as ONE message even when linked after inbox cleanup, and an unresolvable platform warns loudly instead of splitting silently. docs/configuration.md documents the request-context lookup. The relay endpoint is the companion durable change (see done status); until it ships, the link-before-cleanup reorder keeps the normal path correct. * no-mistakes(document): Document X-mode platform recovery (cherry picked from commit 5f808cc) * fix(composer): handle ANSI ghost text safely (kunchenguid#429) * fix(composer): one ANSI-aware ghost owner covers claude dim + grok truecolor Away-mode injection wedged all night on the primary claude-on-herdr pane: the herdr composer classifier never stripped generic dim ghost text (only a narrow codex bold-wrapped byte-pattern check), so claude's rotating prompt-suggestion ghost - a bare "❯" then SGR-2 dim text, which herdr's ANSI pane read preserves - read as real pending input and every escalation deferred (6524 lifetime "pending input (non-empty composer)" defers; wedge 30623s). Consolidate ghost extraction into one fleet-wide ANSI-aware owner, fm_composer_strip_ghost (bin/fm-composer-lib.sh), that drops every de-emphasised run - dim/faint (SGR 2: claude, codex) AND a dark/muted truecolor foreground (grok's placeholder, luminance below FM_COMPOSER_GHOST_LUMA_MAX, default 128, dark-theme assumption). Both ANSI-capable backends route through it: fm_tmux_composer_state (fm_tmux_strip_ghost is now a thin adapter) and fm_backend_herdr_composer_state. The herdr-only faint byte-pattern check is removed and fm_backend_herdr_strip_ansi reduced to a thin adapter over the shared fm_composer_strip_ansi. Bordered detection now reads the plain row so a dark box border dropped with the ghost does not lose the composer shape. This also closes the documented grok TRUECOLOR placeholder gap by the same mechanism (harness-adapters skill note updated). Empirical evidence (read-only live capture + isolated tmux, no herdr lifecycle) and the incident write-up are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh). Two prior ghost-test fixtures that used a near-black 38;2;1;2;3 as "real" colored text (never a realistic real-input color) are corrected to a bright 38;2;224;222;244, preserving the truecolor payload-skip parser intent. * no-mistakes(review): Preserve dark shell prompt safety * no-mistakes(review): Harden erased shell prompt classification * no-mistakes(document): Document shared composer ghost extraction * no-mistakes(lint): Normalize tmux comment punctuation (cherry picked from commit 0eaf293) * fix(spawn): make tmux window handling robust under non-default config (kunchenguid#134) (cherry picked from commit 6d90240) * test: isolate session-start suite from ambient harness markers (kunchenguid#432) * fix(session-start): isolate harness env markers in suite runner Neutralize CLAUDECODE, PI_CODING_AGENT, and GROK_AGENT in run_session_start so ambient interactive shells cannot override the suite's fake ps harness (local-vs-CI split on the pi supervision case). * no-mistakes(document): Correct Pi marker documentation (cherry picked from commit 3e3dff6) * fix: complete brief help and consolidate documentation (kunchenguid#438) * docs: de-feature the scripts.md and CONTRIBUTING test inventories Slice 1 of the documentation redundancy cleanup wave (firstmate scope). docs/scripts.md: every row is now one purpose clause; script headers are the declared owner of behavior, flags, and contracts. Coverage stays 61/61 scripts; bytes drop 19,922 -> 7,958. CONTRIBUTING.md: the 54-row per-test inventory is gone; contributors discover tests by listing tests/*.test.sh and reading each script's own header, and gated tests print their own skip gates. The run commands, symlink assertions, and watcher smoke line are unchanged. Lines drop 135 -> 84 (18,797 -> 7,831 bytes). Two facts that existed only as inventory rows moved into their owners' headers first: fm-brief.sh's paused-vs-blocked scaffold distinction and fm-session-start.sh's Pi extension-loaded check. No instruction-surface or behavior change; AGENTS.md untouched. * no-mistakes(review): Captain, fix brief help and Grok test discovery * no-mistakes(review): Captain: document Grok lock-holder test coverage (cherry picked from commit 492c937) * fix: detect Git and centralize backend configuration (kunchenguid#445) * docs: consolidate universal backend contracts into configuration.md Slice 2 of the documentation redundancy cleanup wave (firstmate scope). docs/configuration.md is now the declared single owner of three universal contracts, each with an explicit ownership sentence: - the universal toolchain list (Toolchain), now also carrying the per-tool purpose clauses that previously lived only in the tmux guide; - the task-selector vocabulary (Runtime backend); - the tasks-axi compatibility definition (Backlog backend). The five backend guides' prerequisites replace their verbatim universal-requirements parentheticals (5 full copies) with a pointer plus only backend-specific items; zellij/cmux selector restatements and architecture.md's partial copy become pointers or are dropped; CONTRIBUTING's compatibility sentence becomes a pointer; two near-verbatim orca-bootstrap restatements (configuration.md Runtime backend, orca guide) collapse into the Toolchain owner copy. Backend-specific setup, behavior, target-string shapes, and every empirical verification record are untouched. AGENTS.md untouched (slice 3). * docs: include git and GitHub auth in the toolchain owner list The review flagged that the new universal-toolchain owner omitted git and GitHub authentication while every backend guide now defers its prerequisites here; bootstrap's NEEDS_GH_AUTH check makes them real universal requirements. * no-mistakes(review): Detect Git in bootstrap toolchain * no-mistakes(document): Clarify GitHub CLI and centralize selector documentation (cherry picked from commit bc558c6) * feat(daemon): add backend-independent wedge alerts (kunchenguid#444) * feat(daemon): backend-independent active alert for the wedge alarm When away-mode injection wedges past max-defer, inject_wedge_alarm only actively signalled via the tmux status-line, which is skipped on non-tmux backends. A wedged claude-on-herdr primary left only the passive state/.subsuper-inject-wedged marker (2026-07-10 overnight incident). Add a config-gated active alert (config/wedge-alarm, local/gitignored; FM_WEDGE_ALARM_CHANNEL) that reaches the captain even when every pane and its status-line is unreadable: an OS-level macOS notification (osascript), a herdr notification, or a captain-supplied command. Default-on (auto) so the alarm is never silent; each channel best-effort, degrading to the next and never crashing the daemon loop. The tmux flash and durable marker stay. The OS notifiers route through a single FM_WEDGE_ALARM_EXEC seam. When the daemon is sourced (only tests do this; production execs it) the seam defaults to "discard", and tests/wake-helpers.sh points it at a recorder, so it is structurally impossible for any test to post a real notification. Channels verified once manually on macOS 26.5.2 / herdr 0.7.3; see docs/wedge-alarm.md. * no-mistakes(review): Bound wedge alarm notifier execution * no-mistakes(review): Captain: harden wedge alarm notifier safety * no-mistakes(review): Captain: harden wedge alarm test notifier isolation * no-mistakes(review): Captain: harden wedge alarm throttling * no-mistakes(review): Redact wedge alarm directive logs * no-mistakes(review): Harden wedge alarm notifier safety * no-mistakes(review): Track notifier process groups through cleanup * no-mistakes(document): Document wedge-alarm active alert behavior (cherry picked from commit 52241a5) * fix(successor): carry reply-platform context through X relinks Integration reconciliation: upstream's platform-aware fm-x-link.sh requires carried reply context on relink, so the successor's X-link carry now forwards the predecessor's x_platform/x_reply_max_chars, defaulting a pre-platform link to the X budget instead of halting the watchdog. * no-mistakes(review): Captain, restore cursor/hermes tmux detection * no-mistakes(test): captain: fix watcher test regressions * test(turnend-guard): restore PR #7 project-dir fallback coverage Rebase reconciliation: re-port the unset-CLAUDE_PROJECT_DIR fallback and physical-identity tests that the Pi logical-run test replay had displaced, on top of the logical-run suite. * no-mistakes(test): Hermeticize turnend guard harness tests * no-mistakes(document): Sync session-start doc comments --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com> Co-authored-by: fmtest <fmtest@example.invalid> Co-authored-by: Pierre Marais <pierremarais67@gmail.com>
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
Fix a fleet-critical bug: away-mode escalation injection wedged all night on the primary claude-on-herdr pane because the composer classifier false-read de-emphasised ghost/placeholder text as real pending input (6524 defers; empirically reproduced read-only from the live primary pane - claude's rotating prompt-suggestion ghost is a bare '❯' then SGR-2 dim text, which herdr's ANSI pane read preserves; the tmux path already stripped dim but the herdr path only had a narrow codex bold-wrapped byte-pattern check that claude's non-bold prompt never matched).
Per explicit captain direction, consolidate ghost-text stripping into ONE ANSI-aware classification owner that both ANSI-capable backends route through, instead of per-harness one-offs. Key decisions and deliberate tradeoffs a diff reader would not know:
Evidence and the incident write-up (exact ANSI bytes, dates, versions, commands) are in docs/herdr-backend.md; deterministic regressions feed the exact captured bytes through the real classifiers (tests/fm-backend-herdr.test.sh, tests/fm-composer-ghost.test.sh); the harness-adapters skill's grok gap note and docs/configuration.md are updated.
What Changed
fm-composer-lib.shso tmux and Herdr ignore dim/faint suggestions and dark truecolor placeholders while retaining real typed input.unknown, preventing away-mode injection into an exited shell.Risk Assessment
Testing
The supplied baseline full suite had passed. Focused classifier regressions and a controlled end-to-end ANSI transcript confirm SGR-2 and dark truecolor ghost text classify as empty on tmux and Herdr, while bright bordered input remains pending; the transcript also verifies the configured luminance threshold behavior.
Evidence: ANSI composer classifier end-to-end transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
🔧 **Rebase** - 1 issue found → auto-fixed ✅
docs/herdr-backend.md- merge conflict rebasing onto origin/main🔧 Fix applied.
✅ Re-checked - no issues remain.
🔧 **Review** - 1 issue found → auto-fixed (2) ✅
bin/fm-tmux-lib.sh:101- Captain, a dark truecolor dead-shell prompt such as$is stripped before classification, turning the safety result fromunknownintoempty. AFK escalation may then type and submit into an exited shell. Preserve or re-check unbordered shell-prompt shape from the plain row before classifying stripped content.🔧 Fix: Preserve dark shell prompt safety
1 error still open:
bin/fm-composer-lib.sh:185- Captain, the new safety backstop recognizes only an exact plain$,>,%, or#. A dead tmux shell prompt such asuser@host $rendered entirely in a dark truecolor is stripped to empty, does not match this check, and then falls through asempty; away-mode can inject and execute its digest in that exited shell. Preserveunknownfor any nonempty unbordered plain row that ghost stripping erased, except verified agent-composer shapes.🔧 Fix: Harden erased shell prompt classification
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Baseline previously successful:command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"bash tests/fm-composer-lib.test.shbash tests/fm-composer-ghost.test.shbash tests/fm-backend-herdr.test.shControlled ANSI fixture harness through production tmux capture and Herdr classifier entry points, recorded incomposer-ansi-e2e-transcript.txt.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.