Skip to content

fix(relay): reject false submit receipts - #343

Open
EtanHey wants to merge 1 commit into
mainfrom
fix/spawn-send-focus-cluster
Open

fix(relay): reject false submit receipts#343
EtanHey wants to merge 1 commit into
mainfrom
fix/spawn-send-focus-cluster

Conversation

@EtanHey

@EtanHey EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Verify every submitted tracked-agent relay, including allow_busy sends, without issuing a recovery Return for busy/queued delivery.
  • Treat Codex queued-follow-up chrome and dirty composer content as stronger evidence than working/thinking status.
  • Fail requested submit verification closed when screen evidence is unreadable, empty, or never positive across boot prompts and all relay aliases; preserve advisory null only when verification was not requested or for the documented spawn_agent launcher probe.

Real incident coverage

  • Probe B replays the live 541-character allow_busy follow-up that received success while remaining in the Codex composer.
  • Probe E replays the live truncated Messages to be submitted after next tool call / frame, followed by the message moving into the composer without submission.
  • Review-driven coverage proves tracked send_command and unavailable-screen aliases cannot retain the same false-green receipt.

Focus evidence and scope boundary

Live evidence shows workspace selection and global pane focus are distinct: newly spawned panes remained focused:false while explicit surface-addressed boot and prompt delivery completed. The source path does not transiently focus and restore the new spawn surface. Focus placement remains a follow-up and is intentionally not changed here.

Probe D's stale surface/registry cleanup and misleading resync diff are also intentionally excluded from this first PR.

Live evidence came from the installed Homebrew cmuxlayer 0.4.16 bundle at /opt/homebrew/Cellar/cmuxlayer/0.4.16/libexec/dist/index.js, which has no commit stamp and is not identified as e8021c4. This PR's source base is origin/main at e8021c4.

TDD receipts

  • Probe B RED: 1 failed / 15 skipped; GREEN: 1 passed / 15 skipped.
  • Probe E RED: 1 failed / 16 skipped; GREEN: 1 passed / 16 skipped.
  • Unreadable verification RED: 1 failed / 17 skipped; GREEN: 1 passed / 17 skipped.
  • Alias-scope correction RED: 2 failed / 187 skipped; GREEN: 2 passed / 187 skipped.
  • Focused gate: 5 files, 379 tests passed.
  • Full gate: typecheck exit 0, build exit 0, 106 files / 2,330 tests passed.
  • Push hook reran the full 106-file / 2,330-test gate successfully on commit 191b963.
  • git diff --check: exit 0.

Review state

Local CodeRabbit review was bounded and stopped after approximately three minutes while still emitting heartbeats. Its one MAJOR finding—requested verification could return null without positive screen evidence—was fixed with dedicated RED/GREEN coverage. No CRITICAL finding was emitted. PR bot reviews are requested after opening.

Remaining gate

Do not merge or deploy yet. MCP/agent lifecycle code still requires parent-coordinated independent Claude review and real fresh-client runtime verification of the reviewed artifact.


Note

Medium Risk
Changes core relay delivery success semantics for MCP/agent tools; incorrect heuristics could reject valid submits or still miss edge cases, but scope is verification-only with broad test coverage.

Overview
Tightens agent input submit verification so successful delivery is only reported when the terminal screen shows real evidence of submission, addressing false-green receipts (including live Codex allow_busy incidents).

Adds screenShowsQueuedAgentInput to treat Codex’s “Messages to be submitted after next tool call” / chrome as pending input. Verification now treats queued UI and composer text as stronger than a transient working status, and only accepts “verified” when there is no pending evidence.

Fail-closed behavior: when verification is requested, missing/empty/unreadable screens return submit_verified: false instead of ambiguous null (except the advisory spawn_agent launcher probe). send_to now runs submit verification for allow_busy presses Enter, but still skips recovery Return on busy sends.

Tests add Codex queue/composer probes, unreadable-screen failure, and update send_command expectations to error when verification cannot prove submission.

Reviewed by Cursor Bugbot for commit 191b963. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Reject false submit receipts in relay when on-screen verification evidence is absent

  • Submit verification now fails closed (false) when no positive on-screen evidence is found, except for spawn_agent events which return null as before.
  • Adds screenShowsQueuedAgentInput to detect a 'Messages to be submitted after next tool call' section; queued or pending input now blocks a verified result.
  • allow_busy submissions now request submit verification (previously suppressed it), without enabling recovery-enter retry.
  • Risk: any submission where the screen is unreadable or shows only queued/composer content now returns an error with submit_verified=false instead of succeeding.
📊 Macroscope summarized 191b963. 1 file reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Summary by CodeRabbit

  • Bug Fixes

    • Improved submission verification for queued input and working states.
    • Prevented unverified submissions from being reported as successful.
    • Added fail-closed handling when screen evidence is unavailable.
    • Preserved busy-agent behavior without triggering duplicate retries.
  • Tests

    • Expanded coverage for queued input, working states, screen-read failures, retries, and verification outcomes.

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a994601e-b4cf-4e79-8b40-544e623feb20)

@EtanHey

EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@greptileai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

@EtanHey I will review pull request #343.

✅ Action performed

Review finished.

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.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 97c203d3-1e69-410d-9cf5-72d7e46a7402

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Submission verification now recognizes queued agent input, requires positive evidence for non-spawn deliveries, and accepts working or thinking states after pending input clears. Busy relays also verify submissions without recovery retries. Tests cover Codex transitions, screen-read failures, and fail-closed responses.

Changes

Submission Verification

Layer / File(s) Summary
Evidence detection and verification flow
src/server.ts, tests/enter-reliability.test.ts
The server detects queued input and verifies success from cleared pending evidence plus working or thinking state. Tests cover Codex rendering, stepped timers, queued-to-composer transitions, and screen-read failures.
Busy relay verification
src/server.ts, tests/enter-reliability.test.ts
allow_busy deliveries now use submission verification while recovery-Enter retries remain disabled.
Fail-closed regression coverage
tests/false-green-empty-surface.test.ts, tests/server.test.ts
Tests now expect errors, ok: false, submit_verified: false, and zero retries when submission evidence is unavailable.

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

Sequence Diagram(s)

sequenceDiagram
  participant AgentRelay
  participant SubmissionVerifier
  participant TerminalSurface
  AgentRelay->>SubmissionVerifier: submit agent input
  SubmissionVerifier->>TerminalSurface: read screen evidence
  TerminalSurface-->>SubmissionVerifier: queued input or working/thinking state
  SubmissionVerifier-->>AgentRelay: verified success or fail-closed error
Loading

Possibly related PRs

Poem

I checked the queue with careful cheer,
Found working thoughts and evidence clear.
No proof? I stop; no false green glow.
Busy paths still verify what they know.
— A reviewing rabbit 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: rejecting false-positive relay submission receipts.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/spawn-send-focus-cluster

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.

Comment thread src/server.ts
);
}

function screenShowsQueuedAgentInput(screenText: string): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High src/server.ts:1744

screenShowsQueuedAgentInput returns true whenever the heading Messages to be submitted after next tool call and a line appear anywhere in the full transcript, including in prior conversation output. If a submitted message or visible reply itself contains those lines (e.g. while discussing or relaying this UI), the function keeps reporting pending queued input after the real composer has cleared, causing submit verification to falsely fail. The regex searches the entire screenText without any boundary tying the heading and marker to the current composer area, so stale matches from earlier output are accepted. Consider anchoring the search to the most recent occurrence of the heading, or otherwise scoping it to the live UI region rather than the whole transcript.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/server.ts around line 1744:

`screenShowsQueuedAgentInput` returns `true` whenever the heading `Messages to be submitted after next tool call` and a `↳` line appear anywhere in the full transcript, including in prior conversation output. If a submitted message or visible reply itself contains those lines (e.g. while discussing or relaying this UI), the function keeps reporting pending queued input after the real composer has cleared, causing submit verification to falsely fail. The regex searches the entire `screenText` without any boundary tying the heading and `↳` marker to the current composer area, so stale matches from earlier output are accepted. Consider anchoring the search to the most recent occurrence of the heading, or otherwise scoping it to the live UI region rather than the whole transcript.

Comment thread src/server.ts
Comment on lines +1747 to +1749
/(?:^|\n)[ \t]*Messages to be submitted after next tool call[ \t]*(?=\n|$)/i.exec(
normalized,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High src/server.ts:1747

The heading regex requires the entire string Messages to be submitted after next tool call on one line. In a narrow terminal pane, Codex wraps that heading across two screen rows, so the regex never matches and screenShowsQueuedAgentInput returns false — even though a queued entry is present. A simultaneous working status is then accepted as a successful submission even though the input is still queued. The regex should tolerate line wrapping within the heading text (or detect the queue entry independently of the heading).

Suggested change
/(?:^|\n)[ \t]*Messages to be submitted after next tool call[ \t]*(?=\n|$)/i.exec(
normalized,
);
/(?:^|\n)[ \t]*Messages\s+to\s+be\s+submitted\s+after\s+next\s+tool\s+call[ \t]*(?=\n|$)/i.exec(
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/server.ts around lines 1747-1749:

The heading regex requires the entire string `Messages to be submitted after next tool call` on one line. In a narrow terminal pane, Codex wraps that heading across two screen rows, so the regex never matches and `screenShowsQueuedAgentInput` returns `false` — even though a queued `↳` entry is present. A simultaneous `working` status is then accepted as a successful submission even though the input is still queued. The regex should tolerate line wrapping within the heading text (or detect the `↳` queue entry independently of the heading).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 191b963cde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server.ts
Comment on lines 3551 to +3555
if (!snapshot) {
return { submit_verified: null, retry_count: retryCount };
return {
submit_verified: noSubmitEvidenceResult,
retry_count: retryCount,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry transient screen reads before rejecting submissions

When the first post-Return readScreen call has a transient socket/connection failure, readParsedSurface converts it to null and this branch immediately reports submit_verified: false instead of polling for the configured verification window. The input may already have been successfully delivered, so callers receive an error and may retry it, producing a duplicate; reserve the failure result for when no later poll provides positive evidence.

Useful? React with 👍 / 👎.

Comment thread src/server.ts
);
}

function screenShowsQueuedAgentInput(screenText: string): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope queued-input detection to actual Codex chrome

Because this helper accepts only the full screen and does not identify a Codex UI region or correlate the queue entry with the submitted text, ordinary visible transcript content can trigger it. For example, a successfully submitted multiline prompt that quotes Messages to be submitted after next tool call followed by an indented line remains visible while the agent is working; it is then classified as pending, suppressing both working-status and cleared-composer success until the operation falsely times out.

Useful? React with 👍 / 👎.

@EtanHey

EtanHey commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Independent Claude review — head 191b963cdeb24ffe7d41862c40608c94927e87d1

Verdict: NOT_SHIP

The diagnosis is right and the direction is right — evidence must outrank status, and requested verification must fail closed. But the ordering change ships a false-negative class broader than the false-positive class it removes, and the new tests cannot see it because they use a hand-authored screen mock while this repo already contains real captured agent frames that contradict the mock's assumptions.

Tests I ran (read-only, at this head, worktree clean before and after):

  • -t 'Probe (B|E)|fails closed when requested agent submission verification cannot read the screen'3 passed, 15 skipped
  • focused gate (5 files) → 379 passed
  • full gate → 106 files, 2330 passed

All three receipts reproduce exactly as claimed. The problems are not in the counts.


CRITICAL — Cursor relays regress from success to hard error.
tests/fixtures/cursor-2026-06-04-task-done.txt is a real capture in this repo. Line 9 shows the agent's answer TASK_DONE already printed — the submit unambiguously happened — and line 13 still shows the submitted prompt inside Cursor's input box. Verified by probe: screenShowsPendingInput(thatFrame, submittedText) === true. With Cursor's own running marker (⬡ Running..., screen-parser.ts:254status="working") the same frame yields status=working and pendingInput=true simultaneously.

Under server.ts:3571-3576 pending evidence now gates the status short-circuit; hasClearedAgentComposer can't fire (box holds the echo); retryEligiblePendingInput requires source_event==="spawn_agent" (3601) so no retry; loop runs the full 5000 ms (server.ts:347) → submit_verified:falseSubmitVerificationError. The message was delivered; the caller is told it wasn't and will resend. Affects send_to, send_to_agent, interact, supersede_agent_goal, dispatch_nudge, tracked send_input/send_command. No test covers Cursor on any changed path.

MAJOR — Codex has no cleared-composer success path. Probe on the real codex-0.144.3-surface-489-working.txt: an empty Codex composer renders its placeholder, so extractComposerInputRegion returns "Implement {feature}", never "". Same for Cursor ("Plan, search, build anything"). So Codex's only positive signal is a transient spinner — a short reply, an approval modal, or an unmatched status line now hard-errors where it previously returned null/ok.

MAJOR — supersede_agent_goal defaults allow_busy:true (10586-10592). Superseding a working agent is exactly the queue-chrome case, so it now throws — and the throw skips supersedePatch (10612+). The pane holds a queued /goal the registry has no record of, and the caller retries. dispatch_nudge has the same flag but catches and stays durable, so that one is fine.

MAJOR — the queue regex is unverified against a real frame. screenShowsQueuedAgentInput (1744-1757) requires the heading to be a bare full line. No raw Probe E capture exists in the evidence set or the repo; the only representation is enter-reliability.test.ts:202, composed by hand. The one real Codex capture here proves Codex draws bordered panels (╭──╮ │ >_ OpenAI Codex …). If the queue block is boxed or suffixed, the guard silently no-ops and Probe E's false green returns in production with all three new tests still green. The Probe B/E tests replay the narrative of the probes, not their bytes — a departure from this repo's own real-capture fixture practice.

MAJOR — the queue guard can false-positive. It scans the whole 30-line window rather than the composer chrome region, and never correlates the item with opts.text. Any Codex pane whose visible transcript contains the heading phrase plus a later line becomes unreachable via send_to — 5 s stall then error, every time. This fleet passes these exact strings around in agent panes.

MINOR — busy sends go from immediate-return to up to 5 s then throw (multiplied across broadcast/dispatch_to_agent fan-outs); CMUXLAYER_SUBMIT_VERIFY_TIMEOUT_MS default is 5000 in code vs 2000 in CLAUDE.md; allow_recovery_enter_retry is unreachable on every path this PR touches, so Probe B's one-Return assertion doesn't actually pin that contract; the allow_busy tool description (10342) no longer matches the behaviour.


Answers to the review questions

  1. Queue chrome false-positive/negative? Both — see the two MAJORs above.
  2. Can working/thinking still outrank pending input? No, correctly fixed. It now also outranks status for CLIs that legitimately retain submitted text — that's the CRITICAL.
  3. allow_busy duplicate Return? Avoided, confirmed. Truthful evidence: yes for Probe B/E, no for supersede (state divergence).
  4. Fail-closed consistent across all listed aliases? Yes — applied at all three exits, every alias routes through deliverInputChunks.
  5. spawn_agent advisory null justified and isolated? Yes to both — source_event:"spawn_agent" is used only for the launcher command (4344, 4357); boot_prompt is a separate event already failing closed via require_working_status.
  6. Do tests reproduce the probes faithfully? No — all three are FakeClaudeSurfaceClient reconstructions. Real captures exist in this repo for exactly this and weren't used.
  7. Compat/latency regressions? Yes — the CRITICAL, the two Codex/supersede MAJORs, and the 5 s busy-send stall.

Required before merge

Scope the pending-composer veto away from retain-on-submit CLIs (the Cursor capture is a ready-made RED); treat known CLI placeholders as cleared; keep supersede's registry patch consistent with the pane; re-derive the queue regex from a captured Probe E frame and commit it; anchor the queue scan to the composer region and prefix-match the item against the sent text; settle the busy-send latency and the timeout-default drift.

Keep as-is: the spawn_agent carve-out, fail-closed on unreadable/empty screens, the busy no-retry contract, and all three new tests — they are correct, just insufficient.

Runtime verification still outstanding

Mock-green is not live-green. Against the built artifact of this head (not the installed 0.4.16): (1) send_to a live working Cursor worker — must succeed; (2) a Codex short-reply relay — no false could not be verified; (3) Probe E replay with a verbatim read_screen(raw:true) capture committed; (4) Probe B replay asserting failure + exactly one Return; (5) supersede a working agent, registry and pane must agree; (6) fan-out latency vs the pre-PR binary.

Full review with line-level evidence and probe transcripts: docs.local/defect-lane/pr343-claude-review.md.

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