Skip to content

fix(send): treat opencode busy-queued composer state as submitted#1

Closed
KostadinP wants to merge 167 commits into
mainfrom
fix/opencode-queued-busy-send
Closed

fix(send): treat opencode busy-queued composer state as submitted#1
KostadinP wants to merge 167 commits into
mainfrom
fix/opencode-queued-busy-send

Conversation

@KostadinP

@KostadinP KostadinP commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Root cause

fm_tmux_submit_enter_core (in bin/fm-tmux-lib.sh) types text, presses
Enter, then verifies delivery by reading the composer row. When the target
opencode pane is mid-turn (busy, footer shows esc interrupt), opencode
accepts the Enter and queues the message for processing when the current
turn ends, but leaves the typed text visible in the composer row. The
verification loop sees a pending composer, exhausts all retries, and
reports the submit as failed with error: text not submitted ... (Enter swallowed; text left in composer) and exits 1 — even though the message
was delivered and will be consumed.

This is a false failure: callers that retry on non-zero exit send the
message twice.

Fix

After the Enter retries are exhausted and the composer still shows
pending, check whether the pane is BUSY using the existing
fm_pane_is_busy (which scans the pane tail for the busy footer regex
esc (to )?interrupt|Working...|Ctrl+c:cancel). If the pane is busy,
the harness has queued the message, so return the accepted verdict
(empty) instead of pending. On an idle pane, keep returning
pending — a genuine swallow must still be detected.

This uses a robust, version-independent signal (the busy footer) instead
of a brittle label match. Retries still run first (a transient render
clears on retry); only the post-retries-verdict is reclassified.

Empirical evidence

opencode version: 1.18.4

Before (busy pane)

Send to a pane showing esc interrupt:

$ FM_HOME=/fleet/firstmate fm-send oc-busy-test:opencode captain on deck
error: text not submitted to oc-busy-test:opencode (Enter swallowed;
       text left in composer; tried ...)
EXIT: 1

Message IS visible in the conversation with QUEUED label and is
consumed when the turn ends.

After (busy pane)

The same scenario now exits 0. Message is delivered exactly once.

Idle genuine-swallow unchanged

An idle pane where Enter does not submit still reports pending and
exits 1. The fix does not mask real swallows.

Regression tests

tests/fm-tmux-submit-busy.test.sh covers four scenarios:

  • busy pane + pending composer after Enter retries -> returns empty
  • idle pane + pending composer after Enter retries -> returns pending
  • busy pane + composer clears on first Enter -> returns empty
  • idle pane + composer clears on first Enter -> returns empty

All mock the underlying tmux commands and override fm_pane_is_busy
with a test stub (controlled by FM_FAKE_PANE_BUSY).

Compatibility notes

herdr backend

fm_backend_herdr_send_text_submit has a parallel gap: when the
baseline agent status is busy (pre-Enter agent was working or
blocked), it falls back to fm_backend_herdr_composer_state for
submit confirmation. An opencode pane that is busy and queues the
message would also return pending after retries. A busy-state check
analogous to the tmux fix would need to consult
fm_backend_herdr_busy_state, but the herdr case is more nuanced
because a blocked agent (stuck waiting on human input) should not
auto-accept. Needs separate investigation.

orca and cmux backends

Both use the same composer-state verification pattern (empty/pending
returned by fm_backend_orca_composer_state /
fm_backend_cmux_composer_state) but have no is_busy primitive. Not
applicable without one.

zellij backend

Uses content-diff (capture before/after Enter) instead of composer
state. If opencode queues a message without changing the pane content,
the diff shows no change — same potential gap, but there is no
is_busy primitive. Not applicable without one.

kunchenguid and others added 30 commits June 26, 2026 09:23
…uid#93)

* feat(send): mark firstmate->secondmate requests for status-path reply

fm-send now prepends a distinct from-firstmate marker (new bin/fm-marker-lib.sh:
the label [fm-from-firstmate] followed by ASCII unit separator 0x1f) when the
resolved target is a bare fm-<id> whose meta records kind=secondmate. The marker
reuses the afk untypable-separator concept but is deliberately distinct from the
daemon's bare leading 0x1f, so it never conflates with a secondmate's own afk
escalation marker.

A secondmate is itself a firstmate, so a relayed request lands in its own chat,
which the main firstmate never reads. The secondmate charter (fm-brief.sh) and
AGENTS.md now document the recognize-and-respond-via-status contract: a marked
request is answered via the status file (or a doc plus a status pointer), never
chat-only; an unmarked message stays conversational captain intervention.

Crewmate/scout targets, explicit session:window targets, and the --key path are
unchanged. Backbone (watcher, daemon, tmux-lib, afk) untouched.

Hermetic tests cover marked vs unmarked sends and the exact marker bytes.

* no-mistakes(document): Sync secondmate marker docs
* fix(teardown): recognize squash-merged work as landed

fm-teardown refused to tear down a worktree whose PR was squash-merged
and whose head branch was then deleted - the most common GitHub flow -
because its safety check used commit reachability (HEAD --not --remotes)
rather than whether the work actually landed. A squash merge writes one
new commit on the default branch, so the branch's own commits are on no
remote-tracking branch, and auto-delete-on-merge drops the head ref too;
reachability was non-empty and teardown false-refused merged work.

For a normal ship task whose commits are not remote-reachable, before
refusing, treat the work as landed if either its PR is merged (resolved
from meta pr= or the branch name via gh-axi; authoritative for squash,
rebase, and merge alike, and surviving branch deletion) or its content
is already in the up-to-date default branch (a 3-way merge-tree that adds
nothing the default branch lacks - robust to the default having advanced
past the merge-base). Dirty worktrees and genuinely unlanded work still
refuse; a gh lookup error falls back to the content check and, if that is
inconclusive, refuses (fail-safe, never silently allows). Fork, local-only,
scout, secondmate, and --force paths are unchanged.

Tests cover squash-merged+deleted-branch (allow), genuinely unlanded
(refuse), dirty (refuse), normally pushed (allow), content-in-default
fallback (allow), and gh-error+content-absent (refuse, fail-safe).

* no-mistakes(review): Guard teardown against stale PR proof

* no-mistakes(review): Verify teardown PR heads

* no-mistakes(document): Sync teardown landing docs

* no-mistakes: apply CI fixes
* fix: harden no-mistakes validation contract against gate-park and self-fix duplication

Rewrite the no-mistakes validation block scaffolded into every ship brief so a
crewmate knows the right move at each gate: the pipeline owns every fix
(including the fix for a real bug review finds in the crewmate's own code);
respond, never self-implement/abort/re-run; the ask-user loop end to end (feed
the decision back via axi respond, do not hand-fix); process every return
(backgrounding ok, never idle-wait for auto-advance); avoid --yes (it
auto-resolves ask-user with zero escalation); and review findings always gate
(review auto-fix is disabled).

Add the supervisor heuristic in AGENTS.md: firstmate keys off the no-mistakes
run step status - running/fixing/ci means working, awaiting_approval/fix_review
means parked (surfaced as awaiting_agent: parked <duration> on axi status) - not
shell liveness, plus a self-fix-duplication red flag (hand-commits/abort/re-run
mid-validation).

Resolves the gate-park-deadlock and self-fix-duplication failure modes.

* no-mistakes(document): Sync validation docs

* no-mistakes(lint): Lint checks clean
* fix(watch): tighten watcher re-arm discipline and assert liveness on drain

The watcher supervision chain could silently lapse: re-arms were bundled
at the tail of multi-command calls (where fm-watch-arm no-ops when a cycle
is briefly still alive, so no fresh cycle got established), and text-only
"holding" turns ran no supervision script, so fm-guard's liveness banner -
which only fires when a guarded script runs - never triggered.

AGENTS.md section 8: make the re-arm rules unambiguous - keep exactly one
live cycle while work is in flight; re-arm after each FIRE (a completed arm
task carrying a wake reason) and never churn on a healthy/started no-op; run
fm-watch-arm standalone, never bundled; never end a turn blind, holds
included. Existing material (singleton lock, beacon, guard banner, afk
exception) is preserved.

fm-wake-drain.sh: assert watcher liveness after draining by reusing
fm-guard.sh's existing graced, beacon-based banner, so a lapse also surfaces
on a plain drain-and-handle turn. Called after the queue is emptied so the
guard never re-prints its own queued-wakes notice, and best-effort so it
never changes the drain's exit status. The grace beacon keeps it silent right
after a normal fire and warns only on a stale-beyond-grace lapse with work in
flight. Watcher core (fm-watch.sh, lock, beacon-touch, wake-queue) untouched.

tests: keep the worktree-tangle check inert across the drain-invoking suites
(wake-helpers points FM_ROOT at a non-git dir, the same trick the direct
fm-guard.sh tests use), and add a regression test asserting the drain warns on
a lapse and stays silent right after a fire.

* no-mistakes(document): Document drain-time watcher liveness
…id#102)

* fix(brief): slim no-mistakes contract to pointer + firstmate wrapper

no-mistakes v1.31.2 self-documents the validation mechanics in its own
SKILL.md (refreshed from the binary on init) and live axi help output,
including the review-always-gates, no-abort/rerun-mid-run, and
never-idle-wait facts. fm-brief.sh's no-mistakes-mode Definition of done
was restating all of that, a drift hazard since the brief lives in a
different repo from the version-matched SKILL.md.

Collapse the duplicated mechanics block to a ~3-line pointer at no-mistakes'
own guidance and keep only the firstmate-specific wrapper: the done
handshake, ask-user escalation to firstmate via rule 6, the --yes captain
stance, and the CI-green reporting line.

* no-mistakes(review): Enforce no-mistakes bootstrap version floor

* no-mistakes(document): Sync no-mistakes docs

* no-mistakes(lint): Fix ShellCheck version parsing
* fix(send): settle codex $skill popup before submit

A `$<skill>` invocation (e.g. $no-mistakes) opens codex's $-autocomplete
popup; submitting too fast lets it swallow the Enter so the invocation
never lands - biting every pipeline trigger to a codex crew/secondmate.
Mirror the existing `/` slash-popup handling: give a `$...` message the
1.2s popup-settle before the (retried) Enter, but scope it to
harness=codex (read from the target's meta) so `$`-prefixed plain text
("$5/month", "$HOME") to claude/opencode/pi keeps the 0.3s fast path. An
explicit session:window target has no meta -> harness unknown -> treated
as non-codex. The retried Enter in fm_tmux_submit_core still backs the
settle up; the `/` case, --key path, marker, and meta-resolution
contract are unchanged.

Record the codex $-popup fact in the harness-adapters skill, and add a
per-harness settle-selection test (codex $ ->1.2, claude $ ->0.3,
explicit $ ->0.3, any / ->1.2, plain ->0.3).

* no-mistakes(document): Sync fm-send popup docs
* feat(supervise): deterministic crew current-state helper

Add bin/fm-crew-state.sh, a deterministic one-line read of a crew's
CURRENT state that ends the stale-status-line friction in supervision.
The status log is an append-only wake-event log, so tail -1 reports the
last event, not the current state: after firstmate resolves a
needs-decision/blocked and the crew silently resumes, the log stays
stale. The helper takes the active no-mistakes run-step as the source of
truth (attributed to the crew's branch, via axi status then the run
list), reconciles a possibly-stale needs-decision/blocked log line
against it (flagging it superseded when the run has resumed), and falls
back to the pane busy-signature + status log when no run is active.
Handles scout/secondmate kinds and torn-down/missing crews gracefully.

Reuses fm-tmux-lib.sh busy detection and meta parsing; fully
deterministic (run-step/pane/log reads only, no heuristics, no LLM).

Wire AGENTS.md sections 7 and 8 to read current state via the helper and
to stop inferring state from a tail of the status log; the status file
stays the wake-event log. Watcher core, status-file/brief contracts, and
wake/dedup machinery are unchanged.

Add tests/fm-crew-state.test.sh covering run-step authority, superseded
stale log lines, genuine-parked, cross-branch attribution, pane and
status-log fallback, scout skip, and torn-down/missing-meta cases.

* no-mistakes(review): Fix dead-window crew state fallback

* no-mistakes(review): Harden crew state liveness checks

* no-mistakes(review): Harden crew state gate handling

* no-mistakes(review): Harden crew state gate parsing

* no-mistakes(document): Document crew state helper

* no-mistakes: apply CI fixes

* fix(crew-state): keep run-step authoritative over a closed pane

The dead-window guard ran before the no-mistakes run lookup, so a
finished crew whose agent had exited and closed its window reported
'unknown' instead of its authoritative run-step state (e.g. done) - the
normal gap between a crew completing and teardown, and a regression
against the helper's core principle of judging by the run-step, not the
shell. Move the pane-readability guard into the no-run fallback path so
it runs only after the run-step lookup: a crew with a run reports its
run-step state regardless of pane liveness, while a genuinely runless
dead window still reports unknown rather than trusting a stale log.

Replace the test that encoded the regression with two that pin the
correct behavior: a closed pane with a terminal run reports done, and a
closed pane with an active run reports working.

* no-mistakes(document): Sync crew-state docs
* feat(watch): always-on wake triage absorbs benign wakes in bash

Factor the afk daemon's classify_signal/classify_stale/heartbeat-scan into a
shared bin/fm-classify-lib.sh used by both the always-on watcher and the daemon.
fm-watch.sh now loops internally: it classifies each wake and absorbs the benign
majority (working: signals, bare turn-ended, non-terminal stale, no-change
heartbeat) by advancing the suppression marker and logging, without queuing or
exiting; it writes the durable queue and exits only on an actionable wake
(captain-relevant signal, any check, terminal stale, a non-terminal stale that
persists past FM_STALE_ESCALATE_SECS, or the heartbeat fleet-scan backstop). So
firstmate re-arms once per actionable event instead of once per wake.

Safety preserved: singleton lock + beacon (touched every poll including while
absorbing), durable queue for actionable wakes, kind=secondmate stale-skip,
per-task check polling, heartbeat backoff, bounded wedge latency. While state/.afk
exists the daemon owns triage and the watcher reverts to one-shot, so the two
never double-triage. AGENTS.md section 8 + afk skill updated; tests added.

* no-mistakes(review): Captain, repair watcher triage edge cases

* no-mistakes(document): Sync watcher triage docs

* no-mistakes: apply CI fixes
…unchenguid#87)

The firstmate side of "listen on X and reply", shipped inside the repo so every
user has it but INERT until they opt in (a non-empty FMX_PAIRING_TOKEN in .env).
Purely additive: the watcher backbone (fm-watch.sh, fm-watch-arm.sh,
fm-wake-lib.sh) and the afk daemon (fm-supervise-daemon.sh, afk skill) are
untouched.

- bin/fm-x-poll.sh: one short-poll of GET /connector/poll; hard no-op without a
  token; requires non-empty text; stashes the full mention (incl. in_reply_to
  conversation context) to state/x-inbox/<id>.json behind a path-traversal guard;
  prints "x-mention <id>" (or a rate-limited "x-mode-error ...") for the watcher.
- bin/fm-x-reply.sh: POST /connector/answer; bearer token via a 0600 header file
  (never argv); reply via --text-file/stdin so mention text is never inlined into
  a shell command. Long replies auto-split into a premium-independent numbered
  "(k/n)" thread (codepoint-aware, capped); single tweet stays unnumbered. Wire:
  {request_id, text}, plus {texts:[...]} for a thread. FMX_DRY_RUN previews to
  state/x-outbox and posts nothing.
- bin/fm-x-lib.sh: .env/env config (token, relay default, dry-run, max chars,
  thread cap; env wins over .env) and the thread splitter.
- bin/fm-bootstrap.sh: .env-presence activation - drop the check shim + 30s
  cadence config on opt-in, remove on opt-out, idempotent, silent off.
- .agents/skills/fmx-respond: public-safe answer playbook - drain inbox, judge
  follow-up worthiness (skip pure acks), conversation continuity via in_reply_to,
  concise by default, dry-run aware.
- AGENTS.md section 14 + README/CONTRIBUTING/docs; tests/fm-x-mode.test.sh
  (hermetic: fake curl, real jq).
* fix(fmx): X-mode mentions are captain instructions - act, then reply

Owner-only relay routing means every routed mention is from the firstmate's
own owner, so fmx-respond no longer frames the asker as a stranger and may
address them as captain. Enabling X mode is the standing authorization, so
replies are composed and posted autonomously with no per-reply confirmation;
dry-run stays the only non-posting path.

A mention's request is a real captain instruction to act on, not merely to
reply to: the drain loop now classifies each mention as an actionable request
(run firstmate's normal lifecycle - intake, backlog, dispatch, scout, ship -
then report the outcome), a question (answer from fleet state), or a pure
acknowledgment (skip). The public channel keeps the yolo carve-out: anything
destructive, irreversible, or security-sensitive is flagged to the captain
through the trusted channel first, never executed straight from a mention.

Public-safety (outcomes only, no secrets/internals), the untrusted-in_reply_to
caution, and the skip-acknowledgment judgment remain intact. AGENTS.md §14
reflects the owner identity, autonomous answering, and act-on-requests carve-out.

* no-mistakes(review): Captain: clarify X-mode consent safeguards

* no-mistakes(review): Captain: document X-mode action consent

* no-mistakes(document): Captain, sync X-mode docs
* fix(fleet-sync): self-heal safe detached drift, loudly flag stuck clones

A pooled clone that drifts off its default branch was silently skipped
forever by both the post-merge teardown sync and bootstrap fleet-sync,
falling further behind on every merge with only an easy-to-miss skip line.

- Auto-recover the one safe case: a clean, detached HEAD that is an
  ancestor of origin/<default> and whose <default> is free to check out
  is re-attached and fast-forwarded, reported as 'recovered:'.
- Every other off-default state (dirty, non-default named branch,
  detached with unique commits, diverged) is left untouched and reported
  as a quantified 'STUCK: ... N commits behind ... - needs attention'
  warning instead of a quiet drift. Nothing is forced, stashed, or discarded.
- Relay the new recovered:/STUCK: outcomes through bootstrap FLEET_SYNC
  lines; document both in AGENTS.md section 3.
- Add tests/fm-fleet-sync.test.sh covering recover, every stuck variant,
  ordinary fast-forward, already-current, local-only/no-origin skips, the
  whole-fleet form, and the bootstrap relay.

* no-mistakes(review): Guard detached recovery from diverged local defaults

* no-mistakes(document): Sync fleet refresh docs

* no-mistakes: apply CI fixes
* feat(x-mode): X-mention completion follow-up flow

Acknowledge an actionable X mention first, do the work, then post one
follow-up reply when it completes.

- fm-x-reply.sh: add --followup mode posting to the relay's
  /connector/followup endpoint; reuses thread-split, payload shape,
  dry-run (with a self-describing endpoint marker), and never-inline
  safety. Answer path unchanged.
- fm-x-link.sh: link a spawned task to its originating mention via
  x_request/x_request_ts in state/<id>.meta (atomic, preserves other
  lines).
- fm-x-followup.sh: --check detection plus post-and-clear on terminal
  completion; honors the 24h window (skip+prune past it), keeps the link
  on a failed post for retry.
- fm-x-lib.sh: shared meta link get/set/clear helpers.
- Docs: fmx-respond reads as one ack-first -> act -> follow-up flow;
  AGENTS.md §14 + supervision pointer document the link, completion
  follow-up, and 24h public-safe window.
- Tests: cover --followup endpoint/payload/dry-run, link, and the
  followup helper; shellcheck clean.

* no-mistakes(review): Captain, fix atomic X meta rewrites

* no-mistakes(document): Document X completion follow-ups
…id#120)

* feat(x-mode): dismiss skipped mentions at the relay

The relay now exposes POST /connector/dismiss: acknowledge a pending
mention without replying - it drops the request, posts nothing, and stops
re-offering it. Wire firstmate to use it on the skip path so a deliberately
unanswered mention no longer churns every poll and times out to the relay's
"offline" auto-reply.

- bin/fm-x-dismiss.sh: new client modeled on fm-x-reply.sh. POSTs
  {request_id} (no body) to /connector/dismiss with the bearer; echoes the
  request_id on 2xx, exits non-zero on non-2xx/transport failure. Honors
  FMX_DRY_RUN (records the would-be POST to state/x-outbox/ with an
  endpoint:"dismiss" marker, posts nothing) and rejects unsafe request_ids.
- fmx-respond skill: the skip path now calls bin/fm-x-dismiss.sh before
  clearing the inbox file; answer and follow-up paths unchanged.
- AGENTS.md section 14: documents that a skipped mention is dismissed at the
  relay, not just locally cleared.
- tests: dismiss posts {request_id} to /connector/dismiss with the bearer
  and echoes it; dry-run records and posts nothing; non-2xx and transport
  failures exit non-zero; unsafe id and bad args rejected.

* chore(no-mistakes): run the bash suite directly as the test step

The test step had no configured test command, so it delegated to an agent;
that agent-driven run crashed the no-mistakes daemon mid-step on this repo.
Configure commands.test to run the firstmate behavior suite deterministically
instead, mirroring .github/workflows/ci.yml: iterate every tests/*.test.sh,
run each, and fail the step if any exits non-zero. This removes the agent from
the test step entirely (no crash) and makes the gate's test baseline match CI.
Same pattern myfirstmate uses (commands.test: mix deps.get && mix test).

* no-mistakes(review): Fix X dismiss docs and gate preflight

* no-mistakes(document): Document X dismiss and gate tests
…unchenguid#126)

* feat(watcher): absorb wakes only when the crew is provably working

The no-verb triage path (a bare turn-end, a working: note, a non-terminal
stale) used to be benign by default and surfaced only on a captain-relevant
status verb. A crew that finished but reported through interactive pane menus
(no done: status) had its final turn-end absorbed, so firstmate was never
woken and the finish was missed.

Invert the rule: absorb a no-verb turn-end or non-terminal stale ONLY when the
crew shows positive evidence it is still working - its no-mistakes run for its
branch is in an actively-running step, or its pane shows the harness busy
signature. Otherwise surface it so firstmate peeks (done, waiting, or wedged).

- fm-classify-lib.sh: add crew_is_provably_working (reuses fm-crew-state.sh,
  no run-step duplication) and signal_crew_provably_working; FM_CREW_STATE_BIN
  override for tests.
- fm-watch.sh: signal path surfaces a no-verb wake whose crew is not provably
  working (costly check runs only on the no-verb, non-afk path); non-terminal
  stale surfaces immediately when not provably working, else absorbs with the
  wedge timer (run-step read only on first sight of a stale hash).
- afk path unchanged: the watcher stays one-shot and skips the provably-working
  read; the daemon keeps its bounded-latency stale backstop.
- tests: cover every required semantic (mid-pipeline absorb, finished/parked
  surface, no-running-pipeline idle surface, busy absorb, captain-verb surface)
  as classifier unit tests and behavioral watcher runs; queue-safety test for
  the new immediate-surface stale path.
- AGENTS.md section 8: document absorb-only-when-provably-working.

* no-mistakes(document): Sync watcher documentation
* feat(harness): add grok (Grok Build) as a verified crewmate adapter

Empirically verified against grok 0.2.73 and encoded across the machinery:

- fm-harness.sh: detect grok via GROK_AGENT=1 env marker (grok does not set
  CLAUDECODE) and `grok` command-name ancestry.
- fm-spawn.sh: grok launch template (`grok --always-approve "$(cat BRIEF)"`,
  fully autonomous, no permission gate) and a turn-end Stop hook. grok only
  loads project hooks after a manual folder-trust grant, so the hook is a
  single firstmate-owned global hook (~/.grok/hooks/fm-turn-end.json, always
  trusted) that is a guarded no-op unless the workspace holds a per-task
  .fm-grok-turnend pointer; fm-spawn drops that gitignored pointer naming
  state/<id>.turn-ended. Hook stays outside the worktree, needs no trust grant.
- fm-watch.sh + fm-tmux-lib.sh: grok busy signature `Ctrl+c:cancel` (the
  mid-turn cancel hint; ASCII, present iff a turn runs).
- harness-adapters skill: grok facts section (busy, exit=Ctrl+Q x2,
  interrupt=Ctrl+C, skill invocation /<skill>, resume) and /no-mistakes form.

Gating question confirmed: grok invokes /no-mistakes and drives a real
no-mistakes axi run, so grok is usable for no-mistakes-mode tasks. End-to-end
verified through fm-spawn: autonomous launch past the dir picker into the
worktree, brief processed, busy->idle and turn-end signal detected, fm-send
steer lands, clean Ctrl+Q exit and teardown. config/crew-harness is left
unchanged; this only makes grok available as a verified option.

* no-mistakes(review): Captain, harden Grok hook lifecycle

* no-mistakes(review): Captain, make Grok harness test executable

* no-mistakes(review): Captain, bound Grok pointer reads

* no-mistakes(test): Captain, harden crew-state and watcher-lock timing

* no-mistakes(document): Document Grok harness support
* feat(harness): split secondmate harness and inherit primary config into secondmate homes

Add config/secondmate-harness so secondmates can run on a different adapter
than crewmates. fm-harness.sh gains a `secondmate` mode resolving the chain
config/secondmate-harness -> config/crew-harness -> own; `crew` mode is
unchanged. fm-spawn resolves a --secondmate launch through that mode (durable:
every respawn re-resolves), while an explicit per-spawn harness arg still wins
and the unverified-adapter guard still holds.

Add a generic, extensible inheritable-config mechanism (fm-config-inherit-lib.sh)
that pushes the primary's declared LOCAL config into each secondmate home's
config/ at secondmate spawn and on the bootstrap secondmate sweep. Exactly one
item is wired today: config/crew-harness, so a secondmate's own crewmates use
the primary's setting. Primary-authoritative (re-pushed every convergence,
mirrors absence); config/secondmate-harness is deliberately not inherited since
secondmates never spawn secondmates. config/ is gitignored, so this is a copy
separate from the tracked-files fast-forward.

Update AGENTS.md (layout, bootstrap, harness, spawn), the harness-adapters
skill, docs/scripts.md, and .gitignore. New tests cover secondmate resolution
and fallback, spawn/respawn honoring config/secondmate-harness, config
propagation on spawn and sweep, the unverified-adapter guard, and backward
compatibility.

* no-mistakes(review): Surface inherited config propagation failures

* no-mistakes(review): Harden inherited config propagation

* no-mistakes(review): Document literal harness inheritance requirement

* no-mistakes(document): Document secondmate harness config
* feat(backlog): default to tasks-axi backend

* no-mistakes(document): Sync backlog backend docs
… /tmp (kunchenguid#36)

* fix(spawn): set per-task GOTMPDIR so interrupted Go builds don't leak /tmp

Go's GOTMPDIR is unset, so every go build/test creates numbered /tmp/go-build*
dirs. Go cleans them on a clean exit but LEAVES THEM when interrupted (signal,
timeout, OOM, full disk), accumulating and filling the disk over time.

Give each task its own temp root at /tmp/fm-<id>/ with Go's build temp nested at
gotmp/. fm-spawn creates the dir (Go won't mkdir GOTMPDIR), exports GOTMPDIR into
the crewmate pane so the agent and child processes inherit it, and records
tasktmp= in meta. fm-teardown reads tasktmp= and removes the whole root on
cleanup, deterministically.

GOTMPDIR (not TMPDIR) is the targeted knob: TMPDIR is too broad (affects every
program's temp). The nested root is extensible: other per-task temp can live
under /tmp/fm-<id>/ later.

Backward compat: tasks spawned before this change have no tasktmp= in meta;
teardown tolerates the empty value as a no-op. The daily fm-disk-cleanup.sh cron
remains a safety net for any pre-fix stray dirs.

* fix(tests): silence SC2016 for literal grep -F patterns in fm-gotmp test

The structural grep -F assertions deliberately match literal $TASK_TMP in the
fm-spawn source; add per-line shellcheck disable=SC2016 (the codebase's existing
pattern, e.g. bin/fm-spawn.sh) so CI lint passes.

* no-mistakes(document): docs: document tasktmp= meta field for per-task GOTMPDIR

---------

Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
* fix(teardown): accept landed squash-merge PR heads

* no-mistakes(document): Document teardown landing behavior

* no-mistakes: apply CI fixes

* fix(test): pass explicit teardown git identity
* feat(dispatch): add dynamic crew profiles

* no-mistakes(review): Captain, document dispatch profile inheritance

* no-mistakes(review): Captain, guard stale dispatch inheritance

* no-mistakes(document): Sync dispatch profile docs

* no-mistakes: apply CI fixes
* Harden crew dispatch profile enforcement

* no-mistakes(document): Captain, synced crew dispatch docs
* feat(config): add live secondmate config push

* no-mistakes(document): Document config push behavior

* no-mistakes(lint): Clean changed shell lint

* no-mistakes: apply CI fixes
* feat(x): add image attachments to reply helpers

* no-mistakes(review): Stream X image replies safely

* no-mistakes(review): Captain, clean X reply temp tracking

* no-mistakes(document): Document X reply image support
* fix(teardown): make landed-check robust when no pr= was ever recorded

fm-teardown.sh's squash-merge landed-check already falls back to
discovering a merged PR by branch name when state/<id>.meta has no
recorded pr=, but nothing guaranteed pr=/pr_head= actually got
recorded on a yolo-authorized merge - the "checks green" trigger that
normally runs fm-pr-check.sh never fires on repos with no PR CI, so a
merge done via a bare `gh-axi pr merge` silently skips it.

Add bin/fm-pr-merge.sh as the one path for merging a task's PR: it
always runs fm-pr-check.sh first, so pr=/pr_head= land in meta as part
of the merge itself regardless of any CI signal. Document both the
existing branch-name discovery fallback and the new merge path in
AGENTS.md, and add regression coverage for the no-pr=-recorded landed
scenario and for fm-pr-merge.sh's record-then-merge behavior.

* no-mistakes(review): Guard PR merges on task metadata

* no-mistakes(document): Document PR merge wrapper

* no-mistakes: apply CI fixes
* Fix fm-pr-merge.sh to parse PR URLs for gh-axi

gh-axi pr merge expects a PR number and --repo, not a full GitHub URL.
Parse the URL, default to --squash when no merge method is passed, and
fail fast on malformed URLs. Tests cover parsing, defaults, and refusal.

* no-mistakes(review): Harden PR merge validation

* no-mistakes(review): Harden PR merge URL guards

* no-mistakes(document): Document PR merge URL handling

* no-mistakes(lint): Clean shell lint
* feat: pin secondmate model/effort in config/secondmate-harness

Extend config/secondmate-harness's format to an optional
"<harness> [<model>] [<effort>]" line so a secondmate can be durably
locked to a concrete model/effort in the same file, without adding a
new config file. A bare harness-only file behaves exactly as before.

fm-harness.sh gains secondmate-model/secondmate-effort accessors;
fm-spawn.sh populates MODEL/EFFORT from them on every secondmate spawn
(including respawns) unless the caller passed an explicit --model/--effort.

* no-mistakes(review): Fix secondmate override pin precedence

* no-mistakes(document): Document secondmate harness pins
* feat(bin): extract tmux runtime behind a backend interface (P1)

Add bin/fm-backend.sh (selection, meta helpers, selector resolution,
dispatch) and bin/backends/tmux.sh (the tmux adapter), then route
fm-send.sh, fm-peek.sh, fm-watch.sh, fm-spawn.sh, and fm-teardown.sh
through them. Every default tmux command sequence, meta shape, and
printed output stays byte-identical: missing backend= still means
tmux, and a default spawn never writes backend=tmux.

Adds a --backend flag (tmux-only for now) and FM_BACKEND/config/backend
selection, refusing any unimplemented backend loudly. Names the
watcher's poll loop as the default event-source implementation over the
backend's pull primitives, per the herdr-addendum's events-as-the-core-
abstraction direction, without changing its behavior.

Verification: fake-tmux/treehouse old-vs-new command-log conformance
tests for send/peek/spawn/teardown, a real-tmux smoke test for the
adapter, and the full existing suite passing unmodified (bar two
fixture-only additions in fm-gotmp.test.sh for the new sibling
scripts).

* no-mistakes(review): Captain, harden backend baseline resolution

* no-mistakes(review): Captain, ignore and document backend config

* no-mistakes(review): Captain, make backend tests executable

* no-mistakes(document): Sync runtime backend documentation
* feat(bin): add experimental herdr runtime backend (P2)

Implements bin/backends/herdr.sh (session-provider adapter, D3: treehouse
stays the worktree provider) wired through fm-backend.sh's dispatch, with
--backend herdr / FM_BACKEND=herdr / config/backend selection, a
version/protocol gate at spawn, semantic busy-state detection via herdr's
agent.get (fm-watch.sh and fm-crew-state.sh consult it before falling back to
the existing tmux pane-regex path), and label-based recovery discovery.

Container shape (D4) decided empirically: tab-per-task in one "firstmate"
workspace, mirroring tmux's one-session-many-windows model.

Found and fixed two real herdr v0.7.1 bugs during verification: `pane read
--lines N` returns empty for small N (worked around by over-fetching and
trimming locally), and `pane get`'s cwd field is frozen at pane-creation time
(fixed to read foreground_cwd instead, needed for fm-spawn's worktree-
discovery poll after `treehouse get`). Also fixed a pre-existing bug in
tests/fm-backend.test.sh's old-vs-new fixture that was silently missing
fm-backend.sh/bin/backends/ from the old bin/ shim.

Full empirical verification, the D4 decision evidence, and a real end-to-end
run (spawn/steer/peek/done/merge-local/teardown, including confirming
teardown refuses before the merge) are recorded in docs/herdr-backend.md.
The entire existing tmux conformance suite stays green.

* no-mistakes(review): Fix Herdr supervision recovery gaps

* no-mistakes(review): Captain, fix Herdr stale recovery gaps

* no-mistakes(review): Document Herdr composer primitive candidate

* no-mistakes(review): Captain, harden Herdr stale recovery and tests

* no-mistakes(document): Sync herdr backend docs

* no-mistakes: apply CI fixes
* feat(bin): auto-detect runtime backend from HERDR_ENV/TMUX markers

fm_backend_name now falls through to runtime auto-detection between
config/backend and the hard tmux default: a firstmate running natively
inside herdr (HERDR_ENV=1) now spawns crewmates into herdr by default,
mirroring how harness detection already works in fm-harness.sh. Nesting
resolves innermost-first (tmux wins over a nested herdr pane). Explicit
--backend/FM_BACKEND/config/backend settings always win over detection.
Selecting herdr via auto-detect prints a loud stderr notice; auto-detecting
tmux stays silent so the unconfigured default path is unchanged.

* no-mistakes(review): Captain, pin tmux tests and backend docs

* no-mistakes(document): Sync backend autodetect docs

* no-mistakes: apply CI fixes
* feat(stow): add operational-memory learnings convention and /stow skill

Add data/learnings.md as the fleet-local operational-learnings home,
a knowledge-routing table in AGENTS.md, and a user-invocable /stow
skill that sweeps a session for uncaptured durable knowledge and
files it to the right disk home before a reset.

* no-mistakes(review): Fix stow backlog note command

* no-mistakes(document): Document stow memory routing
kunchenguid and others added 22 commits July 16, 2026 12:08
…nchenguid#649)

* Clarify concise secondmate registry contract

* no-mistakes(review): Expand secondmate registry boilerplate coverage

* no-mistakes(document): Point route docs to owner
…kunchenguid#654)

* fix(bin): strip quotes on blocked_by in decision-hold resolve

tasks-axi quotes multi-entry blocked_by as "a,b,c", so the comma-boundary
membership test only matched middle elements. Strip surrounding quotes
before matching so first and last hold ids resolve correctly.

* no-mistakes(document): Refresh decision-hold regression evidence
* feat(secondmate): inherit shared captain preferences

* no-mistakes(review): Honor shared captain data overrides

* no-mistakes(review): Honor bootstrap data override registry

* no-mistakes(document): Refresh shared inheritance docs

* no-mistakes(document): Clarify inherited local-material docs
* feat(spawn): gate local agent secret injection

* fix(spawn): align final Keychain slot

* no-mistakes: apply CI fixes
* test: isolate herdr autodetect smoke session

* no-mistakes(review): Restored autodetect smoke gate bypass

* no-mistakes(test): Harden Herdr lab provisioning

* no-mistakes(document): Refresh Herdr lab docs
* fix(pi): distinguish stale locks when arming watcher

* no-mistakes(test): Stabilize watcher extension async waits

* no-mistakes(document): Document Pi lock recovery
* fix: accept secondmate as house vocabulary

* no-mistakes(test): Update captain vocabulary contract test

* no-mistakes(document): Align secondmate documentation vocabulary
…uid#686)

* fix: parse secondmate home after pre-field parentheses

Registry summaries often include parentheticals before the structured
(home: ...) field. Match that field with a greedy prefix so handoff
no longer reports "has no home" for those entries.

* no-mistakes(document): Refresh handoff test comments
* feat: add native session-start nudges

* no-mistakes(document): Document nudge script inventory
…nguid#688)

Relabel absent-captain and related domain defaults wording so it names
the firstmate repo rather than treating "template" as this domain's
identity label. Keep the design-tenet "shared template" statements and
unrelated launch/PR-poll template uses unchanged.
…nsion (kunchenguid#205)

* fix(bin): use set -u-safe empty-array expansion in pr-merge and spawn

Expanding "${arr[@]}" on an empty array under set -u fails on bash < 4.4
(notably macOS bash 3.2). Quote the portable "${arr[@]+"${arr[@]}"}" idiom
in fm-pr-merge and fm-spawn batch dispatch so empty arrays expand to nothing.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(brief): harden fm-brief regression coverage for parse and scaffolds

Tighten bash -n checking, pin literal backtick rendering in the no-mistakes
DOD wording assertion, and keep a scout/secondmate scaffold smoke test so the

Co-authored-by: Cursor <cursoragent@cursor.com>
kunchenguid#166 apostrophe regression cannot return unnoticed.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…nchenguid#693)

* fix: make watcher supervision continuous

* no-mistakes(review): Bound watcher retries and log attached signals

* no-mistakes(review): Add bounded successor-recovery wake fallbacks

* no-mistakes(review): Prevent overlapping successor-arm retries

* no-mistakes(review): Resume supervision after late arm closes

* no-mistakes(review): Bind OpenCode recovery to attempted arm

* no-mistakes(test): Synchronize peer beacon regression fixture

* no-mistakes(test): Synchronize Pi and OpenCode late-close lifecycle fixtures

* no-mistakes(document): Captain: document watcher successor protocol behavior

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes
* fix: always fetch PR head for review diffs

Prefer a freshly fetched refs/pull/<n>/head over a reachable recorded
pr_head= so reviewers never hold a merge over a "missing" fix that already
landed on the remote PR. Recorded SHA is offline fallback only; local branch
is last resort with a warning. Store the tip under refs/fm-review/ so a later
base-branch fetch cannot clobber the compare tip via FETCH_HEAD.

* no-mistakes(test): Isolate session-start nudge tests from gate state

* no-mistakes(document): Correct review-diff documentation
…and skills (kunchenguid#736)

* docs: resolve five contract contradictions

* no-mistakes(test): align owner-pointer assertions with reworded docs; skip absent shellcheck
* docs(harness): reverify grok exit command

* no-mistakes(test): Correct Grok exit resume attribution
* fix(watcher): bound stale wakes for exited paused crew

* no-mistakes(review): Gate pause suppression on confirmed agent death

* no-mistakes(test): Fixed stale pause cadence

* no-mistakes(document): Document dead-agent hold cadence
…id#744)

* fix(supervision): distinguish ordinary wakes from repair

* no-mistakes(review): Make passive guard follow-ups recovery-only

* no-mistakes(document): Clarify recovery-only turn-end guard documentation
* fix(x-mode): dedupe pending mention wakes

* no-mistakes(review): fix x-poll claim error deduplication

* no-mistakes(review): separate claim diagnostics from relay recovery

* no-mistakes(document): Document X-mode once-only mention wakes
…enguid#747)

* feat(wake): enrich drained signal context

* no-mistakes(review): Bound wake enrichment reads

* no-mistakes(document): Document wake-drain annotations

* docs(wake): explain at-least-once drain boundary

* no-mistakes(review): Prevent symlink races in wake annotations

* no-mistakes(review): Exercise wake symlink race regression

* test: document intentional AFK marker subprocesses

* fix(wake): isolate annotation marker state
When fm-send sends a message to a BUSY opencode crewmate on the tmux
backend, opencode accepts the Enter and queues the message for the next
turn, but leaves the typed text visible in the composer row.  The
submit-verification loop sees a pending composer, exhausts retries, and
reports a false "Enter swallowed" failure while the message is actually
delivered.

Fix: after Enter retries are exhausted and the composer still shows
pending, check fm_pane_is_busy.  If the pane is busy (agent mid-turn,
footer shows "esc interrupt"), the harness queued the message, so
return "empty" (accepted).  On an idle pane, keep returning "pending"
(genuine swallow detection preserved).

Regression tests cover four scenarios:
- busy pane + pending composer -> empty (message queued)
- idle pane + pending composer -> pending (genuine swallow)
- busy pane + composer clears on first Enter -> empty
- idle pane + composer clears on first Enter -> empty (existing path)
@KostadinP KostadinP self-assigned this Jul 20, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix tmux send false failures on busy opencode panes (queued Enter)

🐞 Bug fix 🧪 Tests ✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Reclassify tmux submit as accepted when opencode is busy and composer stays pending.
• Add regression tests for busy-queued vs idle-swallowed Enter behavior.
• Expand runtime-backend adapters, docs, and hook/config coverage to prevent drift.
Diagram

graph TD
  Send["bin/fm-send.sh"] --> Backend["bin/fm-backend.sh"] --> TmuxA["bin/backends/tmux.sh"] --> TmuxL["bin/fm-tmux-lib.sh"] --> Tmux[("tmux")]
  Tmux --> Pane{{"opencode pane"}}
  Backend --> Meta[("state/*.meta")]
  Test["tests/fm-tmux-submit-busy.test.sh"] --> TmuxL

  subgraph Legend
    direction LR
    _svc["Script/Lib"] ~~~ _db[("State") ] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Conversation-tail acknowledgement scan
  • ➕ Could provide stronger proof of delivery than composer-state heuristics
  • ➕ May work even if busy footer format changes
  • ➖ More brittle across harness versions/themes/output rendering
  • ➖ Slower and more parsing-heavy; risks false matches
2. Inject unique marker and wait for consumption/echo
  • ➕ Can be deterministic if the harness provides reliable marker semantics
  • ➕ Could unify verification across different backends
  • ➖ Intrusive (pollutes chat/history) and harness-specific
  • ➖ Adds complexity and new failure modes under buffering/streaming UIs

Recommendation: The chosen fix (post-retry reclassification only when BUSY) is the least invasive change that removes the duplicate-send risk. It preserves idle-pane swallow detection while using a version-independent busy footer signal rather than brittle label matching.

Files changed (82) +12742 / -860 · 1 not counted

Enhancement (16) +5796 / -0
fm-composer-lib.shCentralize ghost stripping and composer classification +223/-0

Centralize ghost stripping and composer classification

• Introduces/updates shared composer utilities (ANSI stripping, ghost stripping, and content classification) to keep tmux/herdr adapters consistent and avoid drift.

bin/fm-composer-lib.sh

fm-backend.shRuntime backend selection/detection + dispatch layer +800/-0

Runtime backend selection/detection + dispatch layer

• Adds a backend abstraction framework with known backends, runtime detection signals, selector resolution, and dispatch helpers while preserving tmux as the default when backend= is absent.

bin/fm-backend.sh

tmux.shtmux session-provider adapter (reference backend) +167/-0

tmux session-provider adapter (reference backend)

• Adds a tmux adapter that wraps the existing tmux command sequences into named primitives and re-exports the verified submit core from fm-tmux-lib.sh.

bin/backends/tmux.sh

herdr.shExperimental herdr session-provider adapter +1450/-0

Experimental herdr session-provider adapter

• Adds a herdr backend adapter (CLI+jq) including container/workspace/tab/pane operations and shared policy/lib reuse to keep semantics aligned across backends.

bin/backends/herdr.sh

herdr-eventwait.pyHerdr event-wait helper for event-capable supervision +157/-0

Herdr event-wait helper for event-capable supervision

• Adds a Python helper to subscribe/wait for herdr events in a structured way, supporting event-driven supervision paths and tests.

bin/backends/herdr-eventwait.py

zellij.shExperimental zellij session-provider adapter +642/-0

Experimental zellij session-provider adapter

• Adds a zellij backend adapter implementing the backend primitives for spawn/capture/send/cleanup under explicit backend selection.

bin/backends/zellij.sh

orca.shExperimental Orca backend adapter +336/-0

Experimental Orca backend adapter

• Adds an Orca adapter for send/submit/capture semantics under the runtime-backend abstraction; documents/encodes key support differences where needed.

bin/backends/orca.sh

cmux.shExperimental cmux backend adapter +674/-0

Experimental cmux backend adapter

• Adds a cmux session-provider adapter and wiring consistent with the backend interface, enabling spawn-capable workflows under explicit selection/auto-detect.

bin/backends/cmux.sh

fm-primary-cd-check.jsUpdate opencode cd-check plugin implementation +64/-0

Update opencode cd-check plugin implementation

• Updates opencode plugin logic to match updated cd-check policies and supervision wiring.

.opencode/plugins/fm-primary-cd-check.js

fm-primary-pretool-check.jsUpdate opencode pretool-check plugin implementation +64/-0

Update opencode pretool-check plugin implementation

• Updates opencode plugin logic to match updated pretool-check scripts/policies.

.opencode/plugins/fm-primary-pretool-check.js

fm-primary-sessionstart-nudge.jsUpdate opencode sessionstart nudge plugin implementation +60/-0

Update opencode sessionstart nudge plugin implementation

• Updates sessionstart nudge plugin behavior to align with new onboarding flow and docs.

.opencode/plugins/fm-primary-sessionstart-nudge.js

fm-primary-turnend-guard.jsUpdate opencode turnend guard plugin implementation +97/-0

Update opencode turnend guard plugin implementation

• Updates turnend guard plugin logic to match updated turn lifecycle supervision behavior.

.opencode/plugins/fm-primary-turnend-guard.js

fm-primary-watch-arm.jsUpdate opencode watch-arm plugin implementation +428/-0

Update opencode watch-arm plugin implementation

• Updates watch-arm plugin behavior to align with refactored watcher/backends and arming flow.

.opencode/plugins/fm-primary-watch-arm.js

fm-primary-pi-watch.tsUpdate pi watch extension +376/-0

Update pi watch extension

• Updates the pi extension implementation to remain compatible with the updated supervision/watcher model.

.pi/extensions/fm-primary-pi-watch.ts

fm-primary-turnend-guard.tsUpdate pi turnend guard extension +155/-0

Update pi turnend guard extension

• Updates the pi extension logic for turnend guard to match updated lifecycle tooling and policies.

.pi/extensions/fm-primary-turnend-guard.ts

fm-transition-lib.shShared transition normalization + policy mapping +103/-0

Shared transition normalization + policy mapping

• Adds a shared transition normalization and policy table used by supervision/event-capable adapters to keep status->action mapping consistent.

bin/fm-transition-lib.sh

Bug fix (1) +77 / -97
fm-tmux-lib.shTreat busy queued pending-composer as submitted; share composer classifier +77/-97

Treat busy queued pending-composer as submitted; share composer classifier

• Updates composer parsing to rely on shared fm-composer-lib helpers and expands the busy footer regex. Changes fm_tmux_submit_enter_core so a pending composer after retries returns empty when the pane is busy, preventing false failures and duplicate sends.

bin/fm-tmux-lib.sh

Refactor (1)
fm-send.shDispatch sends through backend abstraction; enforce FM_HOME resolution not counted

Dispatch sends through backend abstraction; enforce FM_HOME resolution

• Refactors fm-send to resolve targets via backend/meta instead of assuming tmux search fallbacks, adds stronger safety checks, and wires marker support for secondmate routing and backend-specific key semantics.

bin/fm-send.sh

Tests (10) +774 / -1
fm-tmux-submit-busy.test.shRegression: busy pane + pending composer after retries returns empty +128/-0

Regression: busy pane + pending composer after retries returns empty

• Adds hermetic tests with a fake tmux to simulate swallowed Enter and persistent pending composer content. Verifies busy panes return empty after retries while idle panes still return pending, and clear-on-first-Enter remains empty.

tests/fm-tmux-submit-busy.test.sh

fm-backend-tmux-smoke.test.shReal tmux smoke test on isolated socket +141/-0

Real tmux smoke test on isolated socket

• Adds a real tmux integration smoke test that validates create/send/capture/resolve/kill primitives without touching host sessions (private socket shim).

tests/fm-backend-tmux-smoke.test.sh

herdr-test-safety.shSafety wrappers for herdr tests +23/-0

Safety wrappers for herdr tests

• Adds guards/wrappers to ensure herdr tests run against isolated sessions and safe cleanup targets.

tests/herdr-test-safety.sh

cmux-test-safety.shSafety wrappers for cmux tests +51/-0

Safety wrappers for cmux tests

• Adds guards/wrappers to ensure cmux tests do not impact a captain’s live environment and target only test scopes.

tests/cmux-test-safety.sh

zellij-test-safety.shSafety wrappers for zellij tests +60/-0

Safety wrappers for zellij tests

• Adds helpers/guards to keep zellij test lifecycle isolated and cleanup safe.

tests/zellij-test-safety.sh

lib.shExtend shared shell test helpers +9/-0

Extend shared shell test helpers

• Updates shared test helpers to support new hermetic tests and backend suites.

tests/lib.sh

wake-helpers.shAdjust wake test helpers for updated supervision paths +73/-1

Adjust wake test helpers for updated supervision paths

• Updates wake-related helpers to remain consistent with refactored watcher/supervision behavior.

tests/wake-helpers.sh

fm-backend-herdr-eventwait.test.pyUnit tests for herdr event-wait helper +106/-0

Unit tests for herdr event-wait helper

• Adds Python tests validating the herdr eventwait helper behavior and integration expectations.

tests/fm-backend-herdr-eventwait.test.py

fm-backend-herdr-eventwait-smoke.test.shSmoke coverage for herdr eventwait integration +129/-0

Smoke coverage for herdr eventwait integration

• Adds shell smoke coverage for wiring between shell scripts and the eventwait helper.

tests/fm-backend-herdr-eventwait-smoke.test.sh

fm-transition-lib.test.shTests for transition normalization/policy +54/-0

Tests for transition normalization/policy

• Adds tests ensuring transition normalization and policy mapping remain stable across changes.

tests/fm-transition-lib.test.sh

Documentation (42) +5498 / -761
tmux-backend.mdAdd tmux backend reference documentation +117/-0

Add tmux backend reference documentation

• Documents tmux as the verified reference runtime backend, including selection behavior, session layout, and operational guidance.

docs/tmux-backend.md

herdr-backend.mdAdd herdr backend verification and operational guide +892/-0

Add herdr backend verification and operational guide

• Adds extensive documentation covering herdr setup, selection/auto-detection, safety constraints, and container shape decisions (workspace-per-home).

docs/herdr-backend.md

zellij-backend.mdAdd zellij backend documentation +224/-0

Add zellij backend documentation

• Documents how to select/use the experimental zellij backend and its known gaps/operational expectations.

docs/zellij-backend.md

cmux-backend.mdAdd/expand cmux backend documentation +374/-0

Add/expand cmux backend documentation

• Documents cmux backend setup and runtime auto-detection signals/fallbacks, plus operational/safety expectations.

docs/cmux-backend.md

orca-backend.mdAdd/expand Orca backend documentation +124/-0

Add/expand Orca backend documentation

• Documents Orca backend selection and behavioral differences versus tmux/herdr, including key support constraints.

docs/orca-backend.md

architecture.mdDocument runtime backend architecture and responsibilities +190/-30

Document runtime backend architecture and responsibilities

• Updates architecture docs to describe the runtime session backend abstraction, adapter responsibilities, and event-source framing.

docs/architecture.md

configuration.mdDocument backend selection configuration and prerequisites +384/-28

Document backend selection configuration and prerequisites

• Updates configuration documentation for selecting runtime backends via config/backend, env, and internal flags; aligns prerequisites with backend support.

docs/configuration.md

AGENTS.mdRestructure and update agent operational documentation +362/-489

Restructure and update agent operational documentation

• Significantly revises AGENTS.md to reflect updated backend/secondmate workflows and operational contracts.

AGENTS.md

README.mdRefresh top-level usage documentation +93/-30

Refresh top-level usage documentation

• Updates the README to match the current operational model (backends, scripts, and workflow expectations).

README.md

CONTRIBUTING.mdUpdate contributing guidance for expanded tooling/tests +31/-31

Update contributing guidance for expanded tooling/tests

• Refreshes contributor docs to align with new scripts, tests, and CI expectations introduced alongside backend refactors.

CONTRIBUTING.md

opencode.mdUpdate opencode supervision protocol docs +21/-0

Update opencode supervision protocol docs

• Refreshes opencode protocol documentation to match updated watcher/send semantics and busy/footer signals.

docs/supervision-protocols/opencode.md

claude.mdUpdate claude supervision protocol docs +23/-0

Update claude supervision protocol docs

• Refreshes claude protocol documentation to align with updated hooks/guardrails and workflow expectations.

docs/supervision-protocols/claude.md

codex.mdUpdate codex supervision protocol docs +15/-0

Update codex supervision protocol docs

• Refreshes codex protocol documentation to align with updated hooks/guardrails and workflow expectations.

docs/supervision-protocols/codex.md

grok.mdUpdate grok supervision protocol docs +39/-0

Update grok supervision protocol docs

• Refreshes grok protocol docs, including busy/cancel footer expectations and hook behavior.

docs/supervision-protocols/grok.md

pi.mdUpdate pi supervision protocol docs +37/-0

Update pi supervision protocol docs

• Refreshes pi protocol docs to align with updated extensions and supervision behavior.

docs/supervision-protocols/pi.md

unknown.mdUpdate fallback supervision protocol docs +12/-0

Update fallback supervision protocol docs

• Updates documentation for unknown/unsupported harness supervision behavior and expectations.

docs/supervision-protocols/unknown.md

turnend-guard.mdDocument turnend guard behavior/configuration +153/-0

Document turnend guard behavior/configuration

• Adds/updates documentation describing turnend guard responsibilities and configuration across harnesses/backends.

docs/turnend-guard.md

sessionstart-nudge.mdDocument sessionstart nudge behavior/configuration +111/-0

Document sessionstart nudge behavior/configuration

• Adds/updates documentation describing sessionstart nudges and their configuration across harnesses.

docs/sessionstart-nudge.md

arm-pretool-check.mdDocument pretool-check behavior/configuration +254/-0

Document pretool-check behavior/configuration

• Adds/updates documentation describing pretool checks and how hooks/policies enforce them.

docs/arm-pretool-check.md

cd-guard.mdDocument cd-guard behavior/configuration +163/-0

Document cd-guard behavior/configuration

• Adds/updates documentation describing cd-guard behavior and how it integrates with hooks/policies.

docs/cd-guard.md

watcher-continuity.mdUpdate watcher continuity documentation +93/-0

Update watcher continuity documentation

• Updates continuity documentation to reflect refactored watcher/supervision behavior under multi-backend support.

docs/watcher-continuity.md

scripts.mdRefresh scripts catalog documentation +82/-31

Refresh scripts catalog documentation

• Updates docs that describe repository scripts, reflecting new backend-related tooling and refactors.

docs/scripts.md

crew-dispatch.jsonUpdate crew dispatch example configuration +24/-0

Update crew dispatch example configuration

• Refreshes example JSON to match current dispatch configuration expectations.

docs/examples/crew-dispatch.json

wedge-alarmUpdate wedge-alarm example +22/-0

Update wedge-alarm example

• Refreshes the wedge-alarm example to match current script behavior and recommended usage.

docs/examples/wedge-alarm

wedge-alarm.mdUpdate wedge-alarm documentation +87/-0

Update wedge-alarm documentation

• Updates wedge-alarm docs to align with current scripts and supervision model.

docs/wedge-alarm.md

codex-app-backend.mdUpdate codex-app backend documentation +211/-0

Update codex-app backend documentation

• Updates documentation clarifying the codex-app backend status/constraints in the runtime-backend model.

docs/codex-app-backend.md

SKILL.mdRevise AFK skill documentation +115/-87

Revise AFK skill documentation

• Updates AFK skill guidance to reflect current supervision/send semantics and operational practices.

.agents/skills/afk/SKILL.md

SKILL.mdExpand bearings skill documentation +81/-0

Expand bearings skill documentation

• Updates bearings skill guidance and examples to match current fleet snapshot/view tooling.

.agents/skills/bearings/SKILL.md

SKILL.mdExpand bootstrap diagnostics skill documentation +52/-0

Expand bootstrap diagnostics skill documentation

• Updates troubleshooting/diagnostic guidance to match refactored bootstrap and backend readiness checks.

.agents/skills/bootstrap-diagnostics/SKILL.md

SKILL.mdDocument decision-hold lifecycle skill +40/-0

Document decision-hold lifecycle skill

• Adds/updates decision-hold lifecycle guidance aligned with updated scripts/docs.

.agents/skills/decision-hold-lifecycle/SKILL.md

SKILL.mdExpand diagnostic reasoning skill documentation +53/-0

Expand diagnostic reasoning skill documentation

• Updates diagnostic reasoning guidance to reflect current tools and expected operator workflow.

.agents/skills/diagnostic-reasoning/SKILL.md

SKILL.mdUpdate Codex App skill documentation +110/-0

Update Codex App skill documentation

• Updates Codex App guidance to align with current docs and backend constraints.

.agents/skills/firstmate-codexapp/SKILL.md

SKILL.mdUpdate internal coding guidelines +88/-0

Update internal coding guidelines

• Refreshes internal coding guidelines to match repository structure and conventions after refactors.

.agents/skills/firstmate-coding-guidelines/SKILL.md

SKILL.mdUpdate Orca skill documentation +91/-0

Update Orca skill documentation

• Updates Orca guidance aligned with experimental Orca backend behavior and limitations.

.agents/skills/firstmate-orca/SKILL.md

SKILL.mdExpand response skill documentation +214/-0

Expand response skill documentation

• Adds substantial guidance for response behavior/contracts under the current supervision and tooling model.

.agents/skills/fmx-respond/SKILL.md

SKILL.mdUpdate harness adapters skill documentation +186/-9

Update harness adapters skill documentation

• Refreshes harness adapter guidance to align with updated backends, hooks, and supervision signals.

.agents/skills/harness-adapters/SKILL.md

SKILL.mdUpdate project management skill documentation +78/-0

Update project management skill documentation

• Updates project-management guidance to reflect current workflow/tooling expectations.

.agents/skills/project-management/SKILL.md

SKILL.mdUpdate secondmate provisioning skill documentation +66/-15

Update secondmate provisioning skill documentation

• Updates secondmate provisioning guidance aligned with new markers, backend support, and lifecycle expectations.

.agents/skills/secondmate-provisioning/SKILL.md

SKILL.mdUpdate stow skill documentation (.agents) +65/-0

Update stow skill documentation (.agents)

• Refreshes internal stow guidance and examples to match current scripts/tests.

.agents/skills/stow/SKILL.md

SKILL.mdUpdate stuck crewmate recovery skill documentation +30/-5

Update stuck crewmate recovery skill documentation

• Updates recovery playbook to align with refactored supervision/backends behavior.

.agents/skills/stuck-crewmate-recovery/SKILL.md

SKILL.mdUpdate updatefirstmate skill documentation +10/-6

Update updatefirstmate skill documentation

• Updates upgrade/sync guidance to reflect current scripts and operational expectations.

.agents/skills/updatefirstmate/SKILL.md

SKILL.mdUpdate stow skill documentation (public) +81/-0

Update stow skill documentation (public)

• Refreshes stow skill guidance outside .agents to keep public docs aligned with current behavior.

skills/stow/SKILL.md

Other (12) +597 / -1
ci.ymlUpdate CI to cover new suites/tooling +56/-1

Update CI to cover new suites/tooling

• Updates CI workflow steps to run the expanded test matrix and enforce updated checks introduced by backend and docs changes.

.github/workflows/ci.yml

.gitignoreUpdate ignored artifacts for new tooling +10/-0

Update ignored artifacts for new tooling

• Adjusts ignore patterns to account for additional generated/test artifacts introduced by new backends and suites.

.gitignore

.no-mistakes.yamlUpdate safety policy configuration +28/-0

Update safety policy configuration

• Updates no-mistakes policy configuration to reflect new/changed files and ownership expectations.

.no-mistakes.yaml

settings.jsonUpdate Claude harness settings +44/-0

Update Claude harness settings

• Updates Claude-related settings to align with new hook/policy/supervision behavior.

.claude/settings.json

hooks.jsonUpdate Codex hook configuration +43/-0

Update Codex hook configuration

• Updates Codex hook definitions to align with new scripts/policies and supervision flow.

.codex/hooks.json

fm-primary-cd-check.jsonUpdate grok cd-check hook config +16/-0

Update grok cd-check hook config

• Refreshes grok hook configuration to match updated guard/check scripts and messaging.

.grok/hooks/fm-primary-cd-check.json

fm-primary-pretool-check.jsonUpdate grok pretool-check hook config +16/-0

Update grok pretool-check hook config

• Refreshes grok pretool check hook config to match updated pretool policy scripts.

.grok/hooks/fm-primary-pretool-check.json

fm-primary-sessionstart-nudge.jsonUpdate grok sessionstart nudge hook config +15/-0

Update grok sessionstart nudge hook config

• Updates sessionstart nudge hook config for grok to match current onboarding flow and docs.

.grok/hooks/fm-primary-sessionstart-nudge.json

fm-primary-turnend-guard.jsonUpdate grok turnend guard hook config +15/-0

Update grok turnend guard hook config

• Updates grok turnend guard hook config to match updated turn lifecycle tooling.

.grok/hooks/fm-primary-turnend-guard.json

package.jsonAdjust opencode plugin package metadata +4/-0

Adjust opencode plugin package metadata

• Updates opencode plugin package configuration to match plugin code and build expectations.

.opencode/plugins/package.json

openai.yamlUpdate Codex App OpenAI agent config +4/-0

Update Codex App OpenAI agent config

• Adjusts agent configuration values/metadata for the Codex App skill.

.agents/skills/firstmate-codexapp/agents/openai.yaml

fm-herdr-lab.shGuarded herdr lifecycle helper for verification +346/-0

Guarded herdr lifecycle helper for verification

• Adds a helper to create/manage isolated herdr sessions/workspaces safely for verification and tests.

bin/fm-herdr-lab.sh

@KostadinP

Copy link
Copy Markdown
Owner Author

Superseded by the no-mistakes pipeline PR.

@KostadinP KostadinP closed this Jul 20, 2026
@KostadinP
KostadinP deleted the fix/opencode-queued-busy-send branch July 20, 2026 19:22
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (4) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 25 rules

Grey Divider


Remediation recommended

1. fm-watch-arm.sh prints extra stdout 📘 Rule violation ≡ Correctness
Description
bin/fm-watch-arm.sh emits status lines not allowed by the checklist (e.g., watcher: attached ...
and alternate FAILED strings) and also prints additional non-status output to stdout. This violates
the requirement that the script emit exactly one well‑formed status line on stdout with the approved
prefixes.
Code

bin/fm-watch-arm.sh[R229-252]

+report_attached() {
  local age
  age=$(fm_path_age "$BEAT")
-  echo "watcher: healthy pid=$HEALTHY_PID (beacon ${age}s)"
+  echo "watcher: attached pid=$HEALTHY_PID (beacon ${age}s)"
+}
+
+# Give a successor the same bounded confirmation window used for a fresh child.
+# Adapter-owned continuations normally win immediately, but the bound avoids a
+# false failure when process-close delivery and lock publication cross briefly.
+wait_for_healthy_successor() {
+  local deadline
+  # date(1) exposes whole seconds. Add one rounding second so a timeout of one
+  # second cannot collapse to a few milliseconds when called near a boundary.
+  deadline=$(( $(date +%s) + CONFIRM_TIMEOUT + 1 ))
+  while :; do
+    healthy_watcher && return 0
+    [ "$(date +%s)" -ge "$deadline" ] && return 1
+    sleep 0.2
+  done
+}
+
+fail_unexplained_cycle() {
+  echo "watcher: FAILED - cycle ended without an actionable reason"
+  return 1
Evidence
The checklist constrains stdout to exactly one status line with approved prefixes. The PR adds/uses
echo "watcher: attached ..." and other non-approved FAILED messages, and the script prints
additional output via print_watch_output, resulting in multiple stdout lines and non-conforming
status strings.

Rule 1769120: fm-watch-arm.sh must emit exactly one well-formed status line
bin/fm-watch-arm.sh[229-252]
bin/fm-watch-arm.sh[425-487]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`bin/fm-watch-arm.sh` must print exactly one approved status line to stdout, but it currently prints `watcher: attached ...`, alternative FAILED lines, and additional watcher output.

## Issue Context
Compliance requires stdout to contain exactly one status line starting with one of: `watcher: started `, `watcher: healthy `, or `watcher: FAILED - no live watcher with a fresh beacon`. Any additional output should be routed to stderr or suppressed.

## Fix Focus Areas
- bin/fm-watch-arm.sh[229-252]
- bin/fm-watch-arm.sh[425-487]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing config/ in .gitignore 📘 Rule violation § Compliance
Description
Root .gitignore does not include an active config/ directory ignore entry; it only ignores
specific config/... files. This violates the requirement that config/ (and other required
directories) be ignored as directories in the root .gitignore.
Code

.gitignore[R11-18]

config/crew-harness
+config/crew-dispatch.json
+config/secondmate-harness
+config/backlog-backend
+config/backend
+config/x-mode.env
+config/cmux-socket-password
+config/wedge-alarm
Evidence
The rule requires a literal config/ entry in the root .gitignore. The current .gitignore shows
projects/, state/, data/, .no-mistakes/, and multiple config/... file patterns, but no
config/ directory pattern.

Rule 1768686: Required directories must be ignored by Git
.gitignore[1-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The compliance checklist requires `config/` (directory) to be ignored in the root `.gitignore`, but the file currently contains only specific `config/...` entries.

## Issue Context
Ignoring only individual files can allow new config files to be accidentally committed.

## Fix Focus Areas
- .gitignore[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. validate_spawn_worktree lacks git-dir check 📘 Rule violation ≡ Correctness
Description
bin/fm-spawn.sh introduces validate_spawn_worktree() but does not implement the required
comparison of git ... rev-parse --absolute-git-dir between the candidate worktree and the primary
checkout. This weakens the mandated isolation validation and does not meet the checklist’s explicit
verification steps.
Code

bin/fm-spawn.sh[R675-691]

+validate_spawn_worktree() {  # <source> <inspect-target>
+  local source=$1 inspect_target=$2 wt_real proj_real wt_top wt_top_real
+  wt_real=
+  if ! wt_real=$(cd "$WT" 2>/dev/null && pwd -P); then
+    wt_real=
+  fi
+  proj_real=$PROJ_ABS_REAL
+  wt_top=$(git -C "$WT" rev-parse --show-toplevel 2>/dev/null || true)
+  wt_top_real=
+  if ! wt_top_real=$(cd "$wt_top" 2>/dev/null && pwd -P); then
+    wt_top_real=
+  fi
+  if [ -z "$wt_real" ] || [ -z "$wt_top_real" ] || [ "$wt_real" != "$wt_top_real" ] || [ "$wt_real" = "$proj_real" ]; then
+    echo "error: $source did not yield an isolated worktree (resolved '$WT'; worktree root '${wt_top:-none}'; primary '$PROJ_ABS'); refusing to launch to avoid tangling the primary checkout. Inspect target $inspect_target" >&2
+    exit 1
+  fi
+}
Evidence
The checklist requires git -C "$worktree" rev-parse --absolute-git-dir and comparing it to the
primary checkout’s absolute git dir, aborting if equal. The added validate_spawn_worktree() only
uses git ... --show-toplevel plus path equality checks and never reads/compares
--absolute-git-dir.

Rule 1768159: Verify fm-spawn uses an isolated Git worktree distinct from the primary checkout
bin/fm-spawn.sh[675-691]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`bin/fm-spawn.sh`'s isolation check should verify the candidate worktree is distinct from the primary checkout by comparing `git -C <path> rev-parse --absolute-git-dir` values (and abort if they match). The current check uses `--show-toplevel` and path comparisons only.

## Issue Context
The compliance checklist explicitly requires git-dir comparison to reliably detect when the resolved path is actually the primary checkout.

## Fix Focus Areas
- bin/fm-spawn.sh[675-691]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. fm-guard.sh missing combined wake warning 📘 Rule violation ☼ Reliability
Description
bin/fm-guard.sh warns about queued wakes independently, but it does not emit a specific warning
when both (a) tasks are in flight and (b) queued wakes are pending. This violates the requirement
for an explicit combined-condition warning.
Code

bin/fm-guard.sh[R210-218]

# Queued wakes are an independent hazard; warn whenever they are pending, even if
# a watcher is alive. Kept after the banner so the no-watcher alarm reads first.
+# Dedup of the watcher-down banner never suppresses this warning.
if "$queue_pending"; then
-  echo "WARNING: queued wakes pending - drain them with bin/fm-wake-drain.sh before anything else." >&2
+  if [ "$READ_ONLY" -eq 1 ]; then
+    echo "WARNING: queued wakes pending - left untouched for the session holding the fleet lock." >&2
+  else
+    echo "WARNING: queued wakes pending - drain them with bin/fm-wake-drain.sh before anything else." >&2
+  fi
Evidence
The checklist requires a warning specifically for the combination of in-flight tasks and pending
wake-queue entries. The code prints an in-flight banner in one block and a queued-wakes warning in a
separate block, without any combined conditional that references both states together.

Rule 1770641: Warn if tasks in flight while wake queue has pending entries
bin/fm-guard.sh[186-218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When there are tasks in flight and `state/.wake-queue` has pending entries, `bin/fm-guard.sh` must emit a dedicated warning mentioning both conditions.

## Issue Context
Current logic prints a queued-wakes warning without checking/mentioning in-flight tasks, so operators don’t get the required combined-state signal.

## Fix Focus Areas
- bin/fm-guard.sh[186-218]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Glyph trim locale bug 🐞 Bug ≡ Correctness
Description
fm_composer_classify_content strips a leading prompt glyph+space using content=${content#??},
which is byte-based under LC_ALL=C and can corrupt multibyte glyph prefixes (❯/›). When that
happens, the post-strip idle-placeholder recheck can fail and the function can incorrectly return
pending instead of empty.
Code

bin/fm-composer-lib.sh[R209-212]

+  case "$content" in
+    '❯ '*|'› '*|'> '*|'$ '*|'% '*|'# '*) content=${content#??} ;;
+    '❯'*|'›'*|'>'*|'$'*|'%'*|'#'*) content=${content#?} ;;
+  esac
Evidence
The code uses fixed-width prefix stripping before trimming and re-checking the idle placeholder
regex; if the prefix strip is wrong, the later idle match won’t fire and the function falls through
to pending. The file also explicitly discusses byte-oriented handling (LC_ALL=C) for multibyte
glyphs, making locale sensitivity a real concern for this code path.

bin/fm-composer-lib.sh[52-86]
bin/fm-composer-lib.sh[182-223]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`fm_composer_classify_content` removes leading glyphs with `${content#??}` / `${content#?}`. In byte-oriented locales (e.g. `LC_ALL=C`), `??` removes two *bytes* (not two characters), which breaks multibyte prompt glyphs like `❯`/`›` and can prevent the second idle-placeholder match from working, producing the wrong verdict.

### Issue Context
This function is a shared owner used by multiple backends, so a locale-dependent misclassification can affect send/inject safety and submit verification.

### Fix Focus Areas
- bin/fm-composer-lib.sh[209-219]

### Suggested fix
Replace the fixed-width removals with explicit prefix removals per glyph (e.g. `content=${content#'❯ '}` / `content=${content#'❯'}` etc.), or compute the matched prefix and strip that exact prefix, avoiding byte-count assumptions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Busy regex false-success risk 🐞 Bug ≡ Correctness
Description
After Enter retries are exhausted, fm_tmux_submit_enter_core now returns empty when
fm_pane_is_busy matches, even if the composer is still pending. Because fm_pane_is_busy greps
for unanchored substrings like Working... in recent pane output, ordinary conversation text
containing those substrings can cause a real swallow to be reported as a successful submit.
Code

bin/fm-tmux-lib.sh[R155-164]

+  # Retries exhausted, composer still shows pending.
+  # If the pane is busy (agent mid-turn), the harness accepted the Enter
+  # and queued the message for processing when the current turn ends.
+  # Treat it as submitted so the caller does not re-send.
+  # On an idle pane, keep reporting pending - a genuine swallow.
+  if fm_pane_is_busy "$target"; then
+    printf 'empty'
+  else
+    printf 'pending'
+  fi
Evidence
The PR adds a new post-retry branch that returns empty based on fm_pane_is_busy.
fm_pane_is_busy scans recent pane output and matches a regex that includes generic text like
Working..., which can appear in non-footer content, so the new branch can convert a real pending
composer into a reported success.

bin/fm-tmux-lib.sh[49-53]
bin/fm-tmux-lib.sh[127-134]
bin/fm-tmux-lib.sh[145-165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`fm_tmux_submit_enter_core` now treats a still-`pending` composer as submitted when `fm_pane_is_busy` returns true. `fm_pane_is_busy` currently matches a broad regex against the last non-blank lines of the pane; if the conversation tail contains a matching phrase (e.g. literal "Working..."), the function may return `empty` even though Enter was genuinely swallowed and the message was not queued.

### Issue Context
This is a new risk because the busy heuristic is now used as a *submit success* signal, not just an observational busy indicator.

### Fix Focus Areas
- bin/fm-tmux-lib.sh[49-53]
- bin/fm-tmux-lib.sh[127-134]
- bin/fm-tmux-lib.sh[145-165]

### Suggested fix
Make the busy check stricter for the queued-submit reclassification, e.g.:
- Anchor the regex to the full footer line (or detect the footer in a known screen region).
- Prefer matching only the last line (or last N lines with stronger structure) rather than any substring in the tail.
- Optionally introduce a dedicated `fm_pane_footer_is_busy` used only for this submit fallback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread bin/fm-watch-arm.sh
}

report_healthy() {
report_attached() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. fm-watch-arm.sh prints extra stdout 📘 Rule violation ≡ Correctness

bin/fm-watch-arm.sh emits status lines not allowed by the checklist (e.g., watcher: attached ...
and alternate FAILED strings) and also prints additional non-status output to stdout. This violates
the requirement that the script emit exactly one well‑formed status line on stdout with the approved
prefixes.
Agent Prompt
## Issue description
`bin/fm-watch-arm.sh` must print exactly one approved status line to stdout, but it currently prints `watcher: attached ...`, alternative FAILED lines, and additional watcher output.

## Issue Context
Compliance requires stdout to contain exactly one status line starting with one of: `watcher: started `, `watcher: healthy `, or `watcher: FAILED - no live watcher with a fresh beacon`. Any additional output should be routed to stderr or suppressed.

## Fix Focus Areas
- bin/fm-watch-arm.sh[229-252]
- bin/fm-watch-arm.sh[425-487]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread .gitignore
__pycache__/
*.pyc
.env
config/crew-harness

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Missing config/ in .gitignore 📘 Rule violation § Compliance

Root .gitignore does not include an active config/ directory ignore entry; it only ignores
specific config/... files. This violates the requirement that config/ (and other required
directories) be ignored as directories in the root .gitignore.
Agent Prompt
## Issue description
The compliance checklist requires `config/` (directory) to be ignored in the root `.gitignore`, but the file currently contains only specific `config/...` entries.

## Issue Context
Ignoring only individual files can allow new config files to be accidentally committed.

## Fix Focus Areas
- .gitignore[1-18]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread bin/fm-spawn.sh
# herdr-sm-spaces-k4). Both branches converge on the same $T ("target") string
# that every downstream operation (send/capture/kill) already treats as opaque
# per-backend routing (fm_backend_resolve_selector).
validate_spawn_worktree() { # <source> <inspect-target>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. validate_spawn_worktree lacks git-dir check 📘 Rule violation ≡ Correctness

bin/fm-spawn.sh introduces validate_spawn_worktree() but does not implement the required
comparison of git ... rev-parse --absolute-git-dir between the candidate worktree and the primary
checkout. This weakens the mandated isolation validation and does not meet the checklist’s explicit
verification steps.
Agent Prompt
## Issue description
`bin/fm-spawn.sh`'s isolation check should verify the candidate worktree is distinct from the primary checkout by comparing `git -C <path> rev-parse --absolute-git-dir` values (and abort if they match). The current check uses `--show-toplevel` and path comparisons only.

## Issue Context
The compliance checklist explicitly requires git-dir comparison to reliably detect when the resolved path is actually the primary checkout.

## Fix Focus Areas
- bin/fm-spawn.sh[675-691]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread bin/fm-guard.sh
[ "$READ_ONLY" -eq 1 ] || fm_guard_clear_stale_banner
fi

# Queued wakes are an independent hazard; warn whenever they are pending, even if

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. fm-guard.sh missing combined wake warning 📘 Rule violation ☼ Reliability

bin/fm-guard.sh warns about queued wakes independently, but it does not emit a specific warning
when both (a) tasks are in flight and (b) queued wakes are pending. This violates the requirement
for an explicit combined-condition warning.
Agent Prompt
## Issue description
When there are tasks in flight and `state/.wake-queue` has pending entries, `bin/fm-guard.sh` must emit a dedicated warning mentioning both conditions.

## Issue Context
Current logic prints a queued-wakes warning without checking/mentioning in-flight tasks, so operators don’t get the required combined-state signal.

## Fix Focus Areas
- bin/fm-guard.sh[186-218]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread bin/fm-composer-lib.sh
printf 'empty'; return 0
fi
# Strip a leading prompt glyph, then re-judge the remainder.
case "$content" in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Glyph trim locale bug 🐞 Bug ≡ Correctness

fm_composer_classify_content strips a leading prompt glyph+space using content=${content#??},
which is byte-based under LC_ALL=C and can corrupt multibyte glyph prefixes (❯/›). When that
happens, the post-strip idle-placeholder recheck can fail and the function can incorrectly return
pending instead of empty.
Agent Prompt
### Issue description
`fm_composer_classify_content` removes leading glyphs with `${content#??}` / `${content#?}`. In byte-oriented locales (e.g. `LC_ALL=C`), `??` removes two *bytes* (not two characters), which breaks multibyte prompt glyphs like `❯`/`›` and can prevent the second idle-placeholder match from working, producing the wrong verdict.

### Issue Context
This function is a shared owner used by multiple backends, so a locale-dependent misclassification can affect send/inject safety and submit verification.

### Fix Focus Areas
- bin/fm-composer-lib.sh[209-219]

### Suggested fix
Replace the fixed-width removals with explicit prefix removals per glyph (e.g. `content=${content#'❯ '}` / `content=${content#'❯'}` etc.), or compute the matched prefix and strip that exact prefix, avoiding byte-count assumptions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread bin/fm-tmux-lib.sh
done
# Retries exhausted, composer still shows pending.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. Busy regex false-success risk 🐞 Bug ≡ Correctness

After Enter retries are exhausted, fm_tmux_submit_enter_core now returns empty when
fm_pane_is_busy matches, even if the composer is still pending. Because fm_pane_is_busy greps
for unanchored substrings like Working... in recent pane output, ordinary conversation text
containing those substrings can cause a real swallow to be reported as a successful submit.
Agent Prompt
### Issue description
`fm_tmux_submit_enter_core` now treats a still-`pending` composer as submitted when `fm_pane_is_busy` returns true. `fm_pane_is_busy` currently matches a broad regex against the last non-blank lines of the pane; if the conversation tail contains a matching phrase (e.g. literal "Working..."), the function may return `empty` even though Enter was genuinely swallowed and the message was not queued.

### Issue Context
This is a new risk because the busy heuristic is now used as a *submit success* signal, not just an observational busy indicator.

### Fix Focus Areas
- bin/fm-tmux-lib.sh[49-53]
- bin/fm-tmux-lib.sh[127-134]
- bin/fm-tmux-lib.sh[145-165]

### Suggested fix
Make the busy check stricter for the queued-submit reclassification, e.g.:
- Anchor the regex to the full footer line (or detect the footer in a known screen region).
- Prefer matching only the last line (or last N lines with stronger structure) rather than any substring in the tail.
- Optionally introduce a dedicated `fm_pane_footer_is_busy` used only for this submit fallback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@KostadinP
KostadinP restored the fix/opencode-queued-busy-send branch July 20, 2026 19:31
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.