Repository navigation
fix(broker): address #1939 must-fix PTY delivery review findings - #1954
khaliqgant wants to merge 8 commits into
Conversation
- M1: latch a terminally failed, already-written delivery: fence its id against replay and hold injection until the composer releases the draft - M2: generic any_output cannot confirm a draft touching the cursor; wrapped-echo compact-tail matches keep same-read activity - M3: delivery_failed leaves BlockedOnSend only while work remains - M4: recovery writes are cancellable; human input withholds queued keys - M5: delivery_verified rejects legacy echo/timeout_fallback PTY frames - M6: empty or unadmitted write_pty does not take composer ownership Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
Devin Review found 2 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ca627bf7d0
ℹ️ 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".
| /// A delivery whose body was written to the composer but never proven | ||
| /// accepted. Its text may still be in the editor, so no further delivery is | ||
| /// injected until the composer is observed without it; otherwise the next body | ||
| /// would append to the failed draft or submit both as one turn. |
There was a problem hiding this comment.
Record the user-visible PTY delivery fixes in Unreleased
This commit changes user-visible broker delivery behavior—failed drafts are fenced, legacy PTY confirmations are rejected, and recovery keys can be cancelled—but leaves the root changelog's empty [Unreleased] section unchanged. Add an impact-first Fixed entry and raise the heading to [Unreleased - Patch] as required for the first pending user-visible fix.
AGENTS.md reference: AGENTS.md:L31-L49
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not adding a separate entry here. These are fixes to the PTY harness-acceptance delivery change from #1891, which has not shipped yet. The changelog entry for that change is being moved into [Unreleased] and rewritten impact-first under #1939's CHANGELOG thread, owned by train-relay-1939. Per AGENTS.md, a fix to an unreleased change belongs in that change's single entry, not a second bullet. I've asked that lane to make sure the entry covers fail-closed delivery for a failed draft and cancellation of recovery keys on human input.
There was a problem hiding this comment.
1 issue found across 7 files
You’re at about 91% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/broker/src/pty_worker.rs">
<violation number="1" location="crates/broker/src/pty_worker.rs:109">
P3: Add the required impact-first `Fixed` entry under `[Unreleased - Patch]` for these user-visible broker delivery changes.</violation>
</file>
| /// injected until the composer is observed without it; otherwise the next body | ||
| /// would append to the failed draft or submit both as one turn. | ||
| #[derive(Debug, Clone)] | ||
| struct FailedComposerLatch { |
There was a problem hiding this comment.
P3: Add the required impact-first Fixed entry under [Unreleased - Patch] for these user-visible broker delivery changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/broker/src/pty_worker.rs, line 109:
<comment>Add the required impact-first `Fixed` entry under `[Unreleased - Patch]` for these user-visible broker delivery changes.</comment>
<file context>
@@ -100,6 +101,54 @@ impl CompletedWorkerDeliveries {
+/// injected until the composer is observed without it; otherwise the next body
+/// would append to the failed draft or submit both as one turn.
+#[derive(Debug, Clone)]
+struct FailedComposerLatch {
+ delivery_id: DeliveryId,
+ event_id: EventId,
</file context>
There was a problem hiding this comment.
Declining here, same rationale as the Codex thread on line 107. These are fixes to the PTY harness-acceptance delivery change from #1891, which has not shipped. That change's single changelog entry is being moved into [Unreleased] and rewritten impact-first under #1939's CHANGELOG thread, owned by train-relay-1939. AGENTS.md wants one impact-first bullet per user-visible change, so a fix to an unreleased change belongs in that entry, not a second bullet. I've asked that lane to cover fail-closed delivery for a failed draft and cancellation of recovery keys on human input.
…ed replay fence - tail anywhere after the cursor, or text right of it, blocks generic acceptance - the failed-draft latch releases only on a provably idle composer, or an operator-wide flush_injections - failed-written delivery ids are retained for the PTY lifetime Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
…gnore cursor-row chrome - an incomplete chunked Codex initial write latches its partial draft - the last terminal delivery failure publishes working/delivery_failed - only body text right of the cursor blocks generic acceptance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
…sive Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
You’re at about 92% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="crates/broker/src/broker/delivery_verification.rs">
<violation number="1" location="crates/broker/src/broker/delivery_verification.rs:287">
P2: A cursor-row placeholder is mistaken for draft content when the delivered message contains the same phrase, so generic output cannot confirm an accepted delivery. Exclude recognized harness chrome independently of `expected_echo` before applying this match.</violation>
</file>
| .char_indices() | ||
| .nth(32) | ||
| .map_or(row.len(), |(index, _)| index); | ||
| compact_render(expected_echo).contains(&row[..probe_end]) |
There was a problem hiding this comment.
P2: A cursor-row placeholder is mistaken for draft content when the delivered message contains the same phrase, so generic output cannot confirm an accepted delivery. Exclude recognized harness chrome independently of expected_echo before applying this match.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/broker/src/broker/delivery_verification.rs, line 287:
<comment>A cursor-row placeholder is mistaken for draft content when the delivered message contains the same phrase, so generic output cannot confirm an accepted delivery. Exclude recognized harness chrome independently of `expected_echo` before applying this match.</comment>
<file context>
@@ -270,6 +270,23 @@ fn tail_near_cursor(snapshot: &Snapshot, tail: &str) -> bool {
+ .char_indices()
+ .nth(32)
+ .map_or(row.len(), |(index, _)| index);
+ compact_render(expected_echo).contains(&row[..probe_end])
+}
+
</file context>
There was a problem hiding this comment.
Declining, with the trade-off now documented on body_text_right_of_cursor. A misread here only fails toward Inconclusive: the delivery is reported unconfirmed and dead-lettered, never falsely confirmed. That's the direction this gate is designed to fail. Recognizing placeholders independently of the body would need a per-harness chrome registry, and the harnesses on this generic path are by definition the ones the broker has no model for. The opposite trade-off is the one Bugbot raised on this same line: dropping body-text matching lets a typed draft be confirmed. The collision also requires the body to contain the same leading 32 compact characters as the placeholder.
…t the cursor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c749f74. Configure here.
…body probe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
…-submission #1945 carried the original #1891 PTY delivery change into main without the #1954/#1955 fixes. Resolved against the #1891 pre-image (origin/trunk, same content as d153837 for these files) so only #1945's own edits conflicted. Kept from #1945: file attachments (wrap-mode attachment references, attachment echo test), CRLF-normalized echo matching, word-boundary "working" busy detection, the delivery_unconfirmed SDK event for rejected delivery_verified frames, and the completed_replay non-confirmation guard. Superseded (orchestrator decision): #1945's M4 variant (event-loop delayed CR follow-up plus refusing human write_pty with pty_write_queue_full during recovery) in favour of #1954's drainer-level cancellation, which never refuses operator keystrokes. #1945's generic echo_left_composer acceptance is replaced by #1954's guarded rule (post-echo output, body not at/after the cursor); attachment deliveries do not depend on the shortcut (confirmed by #1945's author) and are covered by a new test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Session-Id: a4d98f2e-cd4b-47a9-87f8-1deb0f44eb41

Addresses the must-fix (M1–M6) review threads on #1939 (trunk → main, carrying #1891), plus thread #17 (OpenCode
any_output, reassigned here as the OpenCode instance of M2). The other P2/P3 items are handled separately by train-relay-1939.delivery_failed, never a second paste. Injection holds until the composer is provably idle, or until an operator-wideflush_injectionsreleases it explicitly. A tail missing from the viewport is not treated as proof the draft is gone.any_output(including OpenCode) cannot confirm a draft whose tail ends at, straddles, or appears anywhere after the cursor, nor while this body's text sits to the cursor's right. Only known prompt and border glyphs are ignored; a placeholder is not body text. A wrapped-echo compact-tail match keeps the same-read suffix, so its turn marker is not lost.delivery_failedsetsBlockedOnSend(and publishesstuck) only while other deliveries for the worker are pending. Otherwise the worker returns toWorkingand publishesworking, replacing any earlierstuck.delivery_verifiedmust carryharness_acceptanceorcompleted_replay. Legacyecho/timeout_fallbackor unqualified PTY frames are ignored and do not clear custody. Headless confirmations are unchanged.write_ptytakes composer ownership.Human reviewer: please check explicitly
pty_worker.rs,initial_injection_incompletearm). This is the one fix without a dedicated regression test: the arm sits inside the worker select loop, and the repo has no harness that drives that loop. It calls the testedlatch_failed_written_deliveryhelper, so please confirm by reading that the call happens before the arm returns and that the injection text passed is the body that was being written.composer_is_idle), or until an operator-wideflush_injections. Harnesses the broker cannot model as idle will therefore wait for a human flush after a terminal failure. That is fail-closed by design, but it is a behaviour change worth a deliberate sign-off.Verification (local, at
0202e9f81; CI never runs on PRs into trunk:rust-ci.ymlgateschangesto non-PR events or trunk→main)Toolchain:
cargo +1.97.0. The repo has no rust-toolchain pin, and CI installsdtolnay/rust-toolchain@stable(as #1947 does for StarSling). Trunk does not compile on 1.94 because ofatomic_try_update(E0658).Clean env used for broker tests:
env -u RELAY_ATTEST_BROKER_HOOK_PATH -u RELAY_ATTEST_GIT_CONFIG_COUNT -u RELAY_ATTEST_GIT_CONFIG_INDEX -u RELAY_ATTEST_SESSION_ID -u GIT_CONFIG_COUNT. Inside a broker-spawned agent shell, those variables leak into thespawner::git-hook tests; this is independent of this change.Red-first (mutation) checks. Each fix was reverted on its own, then
cargo +1.97.0 test -p <pkg> --lib <filter>was run and the file restored. All 15 went red. The chunked-Codex latch (Bugbot/cubic) has no dedicated test: that arm sits inside the select loop, which has no harness. It reuses the testedlatch_failed_written_deliveryandcomposer_owned_by_deliveryhelpers.composer_owned_by_delivery: drop|| failed_composer_latch.is_some()failed_written_delivery_latches_injection_and_fences_replayfailed_draft_released: returntrueunconditionallyfailed_draft_latch_holds_until_the_composer_releases_itassess_harness_acceptance: drop&& !tail_touches_cursor(snapshot, &tail)generic_repaint_cannot_confirm_a_draft_straddling_the_cursor,opencode_output_cannot_confirm_a_draft_at_the_cursorend_of_compact_match:compact.rfind(..).filter(|_| false)?(no suffix preserved)wrapped_echo_preserves_same_chunk_acceptance_activitylet still_blocked = true;terminal_failure_without_remaining_work_does_not_block_the_workerif is_cancelled() { return Err(write_cancelled()); }cancelled_followup_is_withheld_and_drainer_keeps_running(relay-pty)accepted_delivery_verification: drop theecho/timeout_fallbackand unqualified-PTY armspty_delivery_verified_requires_harness_acceptancehuman_write_takes_ownership: returntrueonly_an_admitted_nonempty_human_write_takes_ownership!tail_near_cursorand!has_visible_text_at_or_after_cursorfrom the generic branchgeneric_output_cannot_confirm_a_long_draft_with_cursor_at_its_startfailed_draft_released: also release when the tail is absent from the viewporta_tail_missing_from_the_viewport_does_not_release_the_latch!has_visible_text_at_or_after_cursorcursor_row_placeholder_does_not_block_generic_acceptance!body_text_right_of_cursorgeneric_output_cannot_confirm_a_draft_whose_tail_scrolled_awaybordered_draft_head_right_of_cursor_stays_inconclusiveCURSOR_ROW_CHROMEsymbolic_draft_head_right_of_cursor_stays_inconclusivestuckstuck/blocked_on_sendlast_failed_delivery_publishes_a_non_stuck_stateKnown follow-up: #1939 P2 (train-relay-1939) adds
composer_holds_tail. Whichever of the two PRs merges second should switchfailed_draft_releasedfromcurrent_composer(..).contains(&tail)to it, so a> brow inside a body cannot release the latch early.Regression tests:
failed_written_delivery_latches_injection_and_fences_replay,failed_draft_latch_holds_until_the_composer_releases_it,generic_repaint_cannot_confirm_a_draft_straddling_the_cursor,opencode_output_cannot_confirm_a_draft_at_the_cursor,wrapped_echo_preserves_same_chunk_acceptance_activity,compact_match_end_maps_back_to_raw_offsets,terminal_failure_without_remaining_work_does_not_block_the_worker,terminal_failure_with_remaining_work_stays_blocked_on_send,cancelled_followup_is_withheld_and_drainer_keeps_running,pty_delivery_verified_requires_harness_acceptance,only_an_admitted_nonempty_human_write_takes_ownership.🤖 Generated with Claude Code
Note
Medium Risk
Changes core PTY injection, acceptance heuristics, and worker blocking behavior; incorrect latch release could stall queues, but the design is intentionally fail-closed with operator flush override.
Overview
Hardens PTY message delivery so relay injections are not mistaken for acceptance and failed bodies are not pasted over or replayed.
Harness acceptance (
delivery_verification) now treats generic post-echo output (e.g. Muse/OpenCodeany_output) as inconclusive while a draft’s tail is near the cursor, body text sits right of the cursor, or wrapped echoes need compact-tail matching.failed_draft_releasedonly clears when the composer is provably idle—not when the tail scrolled off-screen.Snapshot::to_plain_from_cursorsupports those cursor checks.PTY worker adds a failed-composer latch: terminally failed written deliveries fence their id from replay, pause further injection until idle (or operator-wide
flush_injections), and latch on partial chunked writes and human takeover.write_ptyownership applies only to non-empty, drainer-admitted writes. Submit-key recovery is cancellable viarelay-ptyso human input cannot fire a delayed Enter/CR on their draft.Broker runtime ignores PTY
delivery_verifiedwithoutharness_acceptance/completed_replay, and afterdelivery_failedleaves the worker Working when no other deliveries are pending (instead of staying stuck).Reviewed by Cursor Bugbot for commit 0202e9f. Bugbot is set up for automated code reviews on this repo. Configure here.
Agent Relay sessions
claudesessiona4d98f2e· opened viagh pr create· last active 2026-10-10