Skip to content

fix(fm-send): refuse from-firstmate marked slash commands - #91

Merged
trillium merged 3 commits into
mainfrom
fix/robots-u7gu
Aug 7, 2026
Merged

trillium merged 3 commits into
mainfrom
fix/robots-u7gu

Conversation

@trillium

@trillium trillium commented Aug 6, 2026 •

Copy link
Copy Markdown
Owner

Intent

{"summary": "The developer set out to fix a defect in the fm-send message-delivery path so that slash commands tagged with a secondmate marker are refused rather than being sent through as ordinary prose text. Their intent was to add explicit detection of the secondmate marker in fm-send (and its pending-reply library) and block those marked slash commands from delivery. They also wanted regression tests covering this behavior, adding fm-send-popup-settle and fm-send-secondmate-marker test cases. Finally, they intended to update the architecture documentation and the harness-adapters skill guide to reflect the new secondmate command-handling behavior."}

What Changed

  • bin/fm-send.sh now detects when a from-firstmate marked message body is a slash command (or a codex $<skill> command targeting the codex harness) and refuses delivery with a loud error, since the column-0 carrier makes the harness read the line as prose so the command never runs while a pending-reply expectation would otherwise be opened; the recovery guidance points to fm-teardown or an explicit backend target. The popup-settle case now matches on the final $MESSAGE rather than raw args so marked lines take the fast settle.
  • bin/fm-pending-reply-lib.sh adds fm_pending_reply_carrier_body, a byte-exact helper that strips the from-firstmate carrier and any leading corr=<16hex> token so the refusal check reasons about the body the secondmate actually reads (letting already-marked recovery resends be judged on their command).
  • Added regression coverage in tests/fm-send-secondmate-marker.test.sh and extended tests/fm-send-popup-settle.test.sh; updated docs/architecture.md and the harness-adapters skill guide to document the new secondmate command-refusal behavior.

Risk Assessment

✅ Low: A well-bounded guard added ahead of pending-reply creation with behavioral regression tests and docs; unmarked crewmate/explicit-endpoint paths are unchanged and the durable fix covers every marked-send path I could reach.

Testing

Ran the two targeted test files tied to the intent (fm-send-secondmate-marker and fm-send-popup-settle) — all 22 assertions pass. Confirmed the new secondmate-marker test is a true regression by reverting the source to the base commit, where it fails (a marked /exit was delivered, exit 0, instead of refused), then restoring the fix. Captured reviewer-visible end-user evidence as a live fm-send.sh CLI transcript showing marked slash and codex $&lt;skill&gt; commands refused with the explanatory diagnostic and zero bytes typed, while slash-bearing prose and $price text still deliver. Per instructions I did not run the full suite; broad regression remains for CI. This is a CLI-facing change (no rendered UI surface), so the transcript is the appropriate product-level artifact. Temp scripts were removed and the worktree is clean.

Evidence: Live fm-send.sh refusal CLI transcript (robots-u7gu)

$ fm-send fm-domain "/exit" # claude secondmate error: refusing to send the slash command '/exit' to secondmate domain: from-firstmate marked text carries '[fm-from-firstmate]' at column 0 ... [exit code: 1] [composer log bytes typed: 0] $ fm-send fm-cx "$no-mistakes" # codex secondmate skill command error: refusing to send the codex skill command '$no-mistakes' to secondmate cx ... [exit code: 1] [composer log bytes typed: 0] --- control: slash-bearing PROSE still delivers --- [exit code: 0] [composer received: [fm-from-firstmate]corr=... see /Users/x/log for the failure] --- control: codex "$5/month" price still delivers --- [exit code: 0] [composer received: [fm-from-firstmate]corr=... $5/month is cheap]

################################################################
# robots-u7gu: marked secondmate slash-command refusal (live fm-send.sh)
################################################################

$ fm-send fm-domain "/exit"   # claude secondmate
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  2 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the requested message WILL still be sent.
●  watcher supervision needs Stop-owned automatic recovery; inspect the hook registration and startup status before ending the turn.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
error: refusing to send the slash command '/exit' to secondmate domain: from-firstmate marked text carries '[fm-from-firstmate]' at column 0, so the harness reads the whole line as prose and never runs the command. Recover with one of: bin/fm-teardown.sh to close that agent; an explicit backend target (its own endpoint, e.g. session:window), which is never marked, to drive its harness directly; or the same request as prose.
  [exit code: 1]
  [composer log bytes typed: 0]

$ fm-send fm-cx "$no-mistakes"   # codex secondmate skill command
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
error: refusing to send the codex skill command '$no-mistakes' to secondmate cx: from-firstmate marked text carries '[fm-from-firstmate]' at column 0, so codex reads the whole line as prose and never runs the '$<skill>' command. Recover with one of: bin/fm-teardown.sh to close that agent; an explicit backend target (its own endpoint, e.g. session:window), which is never marked, to drive its harness directly; or the same request as prose.
  [exit code: 1]
  [composer log bytes typed: 0]

--- control: slash-bearing PROSE to same secondmate still delivers ---
$ fm-send fm-domain "see /Users/x/log for the failure"
  [exit code: 0]  [composer received: [fm-from-firstmate]⁣corr=e79c475f8d41fafa see /Users/x/log for the failure]

--- control: codex "$5/month" price (not a skill) still delivers ---
$ fm-send fm-cx "$5/month is cheap"
  [exit code: 0]  [composer received: [fm-from-firstmate]⁣corr=d843f952494d0a36 $5/month is cheap]

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 2 infos
  • ℹ️ bin/fm-send.sh:327 - The codex '$<skill>' refusal matches only \$[a-z]*, so a marked codex command whose skill name begins with an uppercase letter, underscore, or digit (e.g. $_DOTFILES, $ApertureOscillation — such skill names exist in this environment) is NOT refused and is still silently delivered as prose to the secondmate: the exact defect class the change fixes. This is an accepted heuristic tradeoff rather than a clear bug — broadening to uppercase would also refuse $HOME/$PATH-style env-var prose, which the author deliberately keeps deliverable (see comment at fm-send.sh:363-366 and SKILL.md:218). The / (universal) form has no such gap. Flagging so the codex-$ narrowing is a conscious choice.
  • ℹ️ bin/fm-pending-reply-lib.sh:228 - The leading corr=&lt;16hex&gt; + blanks stripping block is duplicated verbatim between fm_pending_reply_embed_corr (lines 205-213) and the new fm_pending_reply_carrier_body (lines 228-236). Extracting the shared strip into one helper would remove the copy and keep the two framing/inverse-framing functions in sync if the corr token format ever changes. Non-functional simplification only.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-send-secondmate-marker.test.sh — 13 assertions pass (marked slash/codex-skill refused; lookalike prose, $ prices, non-codex $ bodies, crewmate and explicit-endpoint commands still send)
  • bash tests/fm-send-popup-settle.test.sh — 9 assertions pass, incl. new codex-secondmate $price fast-path settle case
  • Regression proof: reverted only bin/fm-send.sh + bin/fm-pending-reply-lib.sh to base 80058d2 and reran the new test — a slash command to a secondmate should be refused: expected exit 1, got 0 (bug reproduced), then restored to target commit
  • Live end-to-end CLI transcript driving the real bin/fm-send.sh against hermetic claude/codex secondmate targets: /exit and $no-mistakes refused (exit 1, 0 composer bytes); slash-bearing prose and $5/month delivered (exit 0, composer received marked text)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented commands sent through secondmate carriers from being misinterpreted as ordinary text.
    • Added clear rejection for slash commands and Codex $<skill> commands when delivery cannot preserve command parsing.
    • Preserved normal delivery for prose, dollar amounts, and supported direct-target messages.
    • Improved message correlation handling and pending-reply cleanup.
  • Documentation

    • Documented command-delivery limitations and recommended alternatives for direct harness interaction.
  • Tests

    • Added regression coverage for command rejection, message settlement, correlation handling, and valid prose delivery.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 15f75d1f-3605-46a5-b4e3-c0713f591be5

📥 Commits

Reviewing files that changed from the base of the PR and between 6629608 and c893201.

📒 Files selected for processing (3)
  • .agents/skills/harness-adapters/SKILL.md
  • bin/fm-send.sh
  • docs/architecture.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/architecture.md
  • bin/fm-send.sh

📝 Walkthrough

Walkthrough

fm-send now rejects marked secondmate slash commands and Codex $<skill> commands before delivery. It unwraps carrier framing for validation, preserves ordinary prose handling, updates settle classification, documents the rule, and adds regression coverage.

Changes

Secondmate command delivery

Layer / File(s) Summary
Carrier parsing and command validation
bin/fm-pending-reply-lib.sh, bin/fm-send.sh, .agents/skills/harness-adapters/SKILL.md, docs/architecture.md
The carrier body helper removes framing while preserving message content. fm-send rejects marked secondmate commands and classifies settle behavior from the final message. Documentation describes the restriction and alternatives.
Delivery behavior regression coverage
tests/fm-send-secondmate-marker.test.sh, tests/fm-send-popup-settle.test.sh
Tests cover command refusals, correlated resends, prose exceptions, target exceptions, cleanup, diagnostics, and fast popup settling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • trillium/firstmate#90: Both changes modify bin/fm-send.sh and harness-adapter command-delivery behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: refusing from-firstmate-marked slash commands in fm-send.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/robots-u7gu

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/architecture.md`:
- Line 196: Update the documentation sentence around the marked slash-command
refusal to also state that bin/fm-send.sh refuses marked $<skill> commands when
the target harness is Codex. Retain the existing recovery guidance, including
bin/fm-teardown.sh and the never-marked explicit backend target for direct
harness driving.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c898e28-afee-4a19-bf69-9f209be33b9f

📥 Commits

Reviewing files that changed from the base of the PR and between 80058d2 and 6629608.

📒 Files selected for processing (6)
  • .agents/skills/harness-adapters/SKILL.md
  • bin/fm-pending-reply-lib.sh
  • bin/fm-send.sh
  • docs/architecture.md
  • tests/fm-send-popup-settle.test.sh
  • tests/fm-send-secondmate-marker.test.sh

Comment thread docs/architecture.md Outdated
trillium pushed a commit that referenced this pull request Aug 6, 2026
…sh refusal

CodeRabbit on #91: the carrier paragraph documented only the marked slash-command
refusal, but fm-send.sh also refuses a marked $<skill> command when the target's
meta records harness=codex. State that condition so the doc matches the code, and
keep the existing recovery guidance (fm-teardown for a close, the never-marked
explicit backend target to drive the harness directly).

Refs: robots-u7gu
@trillium trillium changed the title fix(fm-send): refuse from-firstmate marked slash commands to secondmates fix(fm-send): refuse from-firstmate marked slash commands Aug 6, 2026
@trillium

trillium commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Re-requesting: the previous run reported success — Review rate limited, so head c2b8672a is unreviewed. The delta from the reviewed head 66296087 is one line of docs/architecture.md (the resolved thread's fix); no shell code changed.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

@trillium I will review pull request #91 at head c2b8672a.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Trillium Smith added 3 commits August 7, 2026 09:27
…ing them as prose

The from-firstmate carrier occupies column 0 of the composer line, and every
verified harness parses a command only when its slash is the line's first
character. A slash command to a kind=secondmate target therefore arrived as
ordinary prose: the agent read it, sometimes narrated compliance, and the
command never ran - while fm-send reported a verified submit and opened a
pending-reply expectation for it. The tell was already in the code: the
popup-settle matched on the PRE-marker arguments, so fm-send paid a 1.2s
slash-popup settle for a line that could not open a popup.

Refuse that send loudly, naming the carrier as the cause and the two working
paths (fm-teardown to close an agent; the never-marked explicit backend target
to drive its harness directly). The check runs on the body the secondmate would
actually read, so an already marked and correlated recovery resend cannot slip
the same command through, and it runs before the pending-reply record is
created, so a refusal leaves no orphan expectation.

Crewmate targets, explicit endpoints, the --key path, and prose that merely
contains or is indented before a slash are all unaffected.

Also select the popup-settle from the final text rather than the arguments, so a
marked line takes the fast path instead of paying a settle for a popup that
cannot open.

Refs: robots-u7gu
…sh refusal

CodeRabbit on #91: the carrier paragraph documented only the marked slash-command
refusal, but fm-send.sh also refuses a marked $<skill> command when the target's
meta records harness=codex. State that condition so the doc matches the code, and
keep the existing recovery guidance (fm-teardown for a close, the never-marked
explicit backend target to drive the harness directly).

Refs: robots-u7gu
@trillium
trillium merged commit 3a68df8 into main Aug 7, 2026
16 checks passed
trillium added a commit to trillium/parlay that referenced this pull request Aug 8, 2026
…-bn5d)

Every rule the gate asserts is evaluated against origin's headRefOid, which is
correct — that is the commit a merge lands. But the caller is a mechanic who
has just authored a fix and pushed it, and for whom READY reads as "my fix is
cleared to merge". `git push no-mistakes` goes to the MIRROR, and the pipeline
pushes on to origin asynchronously.

On trillium/firstmate#91 that gap was live: origin's head was still the pre-fix
commit while the gate returned exit 0 READY. Merging there would have landed the
old head and silently DROPPED the just-authored fix for the reviewer's finding —
the exact premature-FIXED failure the mechanic guardrails exist to prevent, with
the gate pointing at it. Once the pipeline did push, the same PR went 0 -> 4, so
the READY was not merely early; it was the opposite of the eventual answer.

The gate cannot see a mirror or a pipeline run. It can see, from any checkout or
linked worktree of the repo, that the local branch holds commits origin's PR
head does not — the same fact from the side it has access to, and true for a
plain forgotten push too. detectHeadFreshness measures that, pinned to the
already-resolved repo (robots-g4qz) so a same-named branch in an unrelated
checkout can never invent a blocker.

- ahead/diverged -> head-not-pushed, pending-class, exit 5. Nothing is wrong
  with the diff; the commits have not arrived and the answer changes on its own.
- MERGED + ahead -> code-class, exit 3. The stale merge already happened,
  waiting cannot undo it, and `git branch -r --contains` passes for the wrong
  commit.
- behind -> a note only; a stale checkout risks nothing.
- unverifiable -> say so with the reason, and hand over the check the gate could
  not run. "Could not tell" is not "they agree".

Origin's head sha is now printed on every verdict, including the READY line —
the ticket's minimum ask, and the one thing that made the two commits
distinguishable at a glance.

Exit 5 now has two shapes with opposite instructions, so its notes name
whichever is present rather than always printing the review-in-flight script.
Class precedence is unchanged: one code blocker still keeps the whole verdict
at 3.
trillium added a commit to trillium/parlay that referenced this pull request Aug 20, 2026
…-bn5d)

Every rule the gate asserts is evaluated against origin's headRefOid, which is
correct — that is the commit a merge lands. But the caller is a mechanic who
has just authored a fix and pushed it, and for whom READY reads as "my fix is
cleared to merge". `git push no-mistakes` goes to the MIRROR, and the pipeline
pushes on to origin asynchronously.

On trillium/firstmate#91 that gap was live: origin's head was still the pre-fix
commit while the gate returned exit 0 READY. Merging there would have landed the
old head and silently DROPPED the just-authored fix for the reviewer's finding —
the exact premature-FIXED failure the mechanic guardrails exist to prevent, with
the gate pointing at it. Once the pipeline did push, the same PR went 0 -> 4, so
the READY was not merely early; it was the opposite of the eventual answer.

The gate cannot see a mirror or a pipeline run. It can see, from any checkout or
linked worktree of the repo, that the local branch holds commits origin's PR
head does not — the same fact from the side it has access to, and true for a
plain forgotten push too. detectHeadFreshness measures that, pinned to the
already-resolved repo (robots-g4qz) so a same-named branch in an unrelated
checkout can never invent a blocker.

- ahead/diverged -> head-not-pushed, pending-class, exit 5. Nothing is wrong
  with the diff; the commits have not arrived and the answer changes on its own.
- MERGED + ahead -> code-class, exit 3. The stale merge already happened,
  waiting cannot undo it, and `git branch -r --contains` passes for the wrong
  commit.
- behind -> a note only; a stale checkout risks nothing.
- unverifiable -> say so with the reason, and hand over the check the gate could
  not run. "Could not tell" is not "they agree".

Origin's head sha is now printed on every verdict, including the READY line —
the ticket's minimum ask, and the one thing that made the two commits
distinguishable at a glance.

Exit 5 now has two shapes with opposite instructions, so its notes name
whichever is present rather than always printing the review-in-flight script.
Class precedence is unchanged: one code blocker still keeps the whole verdict
at 3.
trillium added a commit to trillium/parlay that referenced this pull request Aug 20, 2026
…-bn5d)

Every rule the gate asserts is evaluated against origin's headRefOid, which is
correct — that is the commit a merge lands. But the caller is a mechanic who
has just authored a fix and pushed it, and for whom READY reads as "my fix is
cleared to merge". `git push no-mistakes` goes to the MIRROR, and the pipeline
pushes on to origin asynchronously.

On trillium/firstmate#91 that gap was live: origin's head was still the pre-fix
commit while the gate returned exit 0 READY. Merging there would have landed the
old head and silently DROPPED the just-authored fix for the reviewer's finding —
the exact premature-FIXED failure the mechanic guardrails exist to prevent, with
the gate pointing at it. Once the pipeline did push, the same PR went 0 -> 4, so
the READY was not merely early; it was the opposite of the eventual answer.

The gate cannot see a mirror or a pipeline run. It can see, from any checkout or
linked worktree of the repo, that the local branch holds commits origin's PR
head does not — the same fact from the side it has access to, and true for a
plain forgotten push too. detectHeadFreshness measures that, pinned to the
already-resolved repo (robots-g4qz) so a same-named branch in an unrelated
checkout can never invent a blocker.

- ahead/diverged -> head-not-pushed, pending-class, exit 5. Nothing is wrong
  with the diff; the commits have not arrived and the answer changes on its own.
- MERGED + ahead -> code-class, exit 3. The stale merge already happened,
  waiting cannot undo it, and `git branch -r --contains` passes for the wrong
  commit.
- behind -> a note only; a stale checkout risks nothing.
- unverifiable -> say so with the reason, and hand over the check the gate could
  not run. "Could not tell" is not "they agree".

Origin's head sha is now printed on every verdict, including the READY line —
the ticket's minimum ask, and the one thing that made the two commits
distinguishable at a glance.

Exit 5 now has two shapes with opposite instructions, so its notes name
whichever is present rather than always printing the review-in-flight script.
Class precedence is unchanged: one code blocker still keeps the whole verdict
at 3.
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.

1 participant