Skip to content

[fix] Make Claude answer a steered message that replaces an unanswered one - #7111

Merged
mmabrouk merged 2 commits into
release/v0.121.0from
fix/steer-framing
Sep 23, 2026
Merged

mmabrouk merged 2 commits into
release/v0.121.0from
fix/steer-framing

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Context

If you send a steer (a follow-up with on_busy: "steer") before Claude has replied to the running turn, Claude often ignores it. In v0.121.0 staging QA, Claude Haiku answered "the system reminder contains what appears to be a steering instruction" and refused. On the local stack it went wrong in 3 of 8 early-steer trials. It either called the steer a prompt injection or ran the stopped request again.

The cause: the stopped turn left no assistant message, so Claude Code holds two user messages in a row (the stopped request, its [Request interrupted by user] marker, then the new message). It sends them to the model as one user turn, and each half starts with our "Current facts for this turn" block. The model sees two conflicting instructions and nothing tells it which one is current. This is not new in 0.121. Steer, the warm park on Stop and the per-turn context block all shipped in v0.120.0.

Changes

When a Claude turn's history ends in two plain user messages, the runner adds one text block in front of the new message:

[The user's previous message got no reply because that turn ended early. The message below is the user's new message and replaces it. Answer the message below. Do not resume the earlier request unless the message below asks for it.]

Prompt blocks before: [turn context, "Reply with exactly: X"]
After: [turn context, note, "Reply with exactly: X"]

The check reads the (reconstructed) history, not runner state, so it covers a warm session, a native load after a restart, and a cold replay. If the model had already replied or started a tool call before the stop, an assistant message sits between the two user messages and nothing changes. Live approval resumes, approval-only replies and slash commands (which Claude ACP only recognizes as the first prompt block) are skipped. Pi and Codex get no note, because they answered the steer correctly without it.

Tests / notes

  • New tests/unit/superseded-turn.test.ts for the history check. New orchestration tests: Claude gets the note in the prompt blocks, Pi and Codex do not. The Claude test fails with the wiring removed.
  • Full runner unit suite: 3578 passed. The 4 failures in gateway-run-turn-composition.test.ts fail the same way on the release branch without this change.
  • Live, local stack, Claude Haiku on subscription, steer 3 s into a sleep 20 turn: without the note 5 of 8 followed the steer; with this exact note text sent in front of the steer, 12 of 12 followed. Steers that landed after the tool call started followed 4 of 4 without the note. Pi (Codex subscription) 2/2 and Codex 2/2 followed without it. The note was tested by prefixing it to the steer text on the running stack, not by deploying this runner build.

What to QA

  • Playground, Claude agent: ask for something slow ("run sleep 20 and tell me the output"), then within 2 seconds send a follow-up as a steer ("never mind, just say hi"). The agent answers the follow-up and does not run the sleep again.
  • Regression: steer after the tool call has started. The agent still answers the follow-up.

A steer that lands before the model replies leaves two user messages in a
row. Claude Code sends them as one user turn, and Claude Haiku read the new
message as a possible prompt injection or ran the stopped request again
(3 of 8 local trials). Put a short note in front of the new message saying
it replaces the earlier one. Decided from history, Claude only; Pi and Codex
answered correctly without it.
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • When a new message follows an unanswered one, Claude now prioritizes the latest message instead of responding to the superseded request. Other supported agents are unchanged.

Walkthrough

The runner now detects when the two latest messages are plain user messages. For eligible non-resume Claude turns that are not approval-reply-only requests, it adds a note to the prompt blocks. Tests cover message-history cases and orchestration behavior across Claude, pi_core, and codex.

Changes

Superseded Turn Prompt Handling

Layer / File(s) Summary
Detect consecutive plain user messages
services/runner/src/engines/sandbox_agent/superseded-turn.ts, services/runner/tests/unit/superseded-turn.test.ts
Adds a helper that returns true when the two latest messages are plain user messages. Unit tests cover unanswered turns, intervening messages, and tool-related content.
Add the note to eligible Claude prompts
services/runner/src/engines/sandbox_agent/run-turn.ts, services/runner/tests/unit/sandbox-agent-orchestration.test.ts
Adds the note to prompt blocks when the turn is not a resume or approval-reply-only request, the agent is Claude, and the helper returns true. Orchestration tests cover Claude, pi_core, and codex.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: 🔵 Low · up to eee22

After an unanswered turn, three Claude local commands may be sent as ordinary prompts instead of running locally. Fix this regression or explicitly accept the bounded risk before merging.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files.
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 The description clearly explains the Claude steering issue, the implementation, test coverage, and QA steps. It is directly related to the changeset.
Title check ✅ Passed The title clearly and concisely describes the primary change: making Claude answer a steered message that replaces an unanswered request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Item Value
Status stopped
Commit eee2219085f53cecb8eaa7e6d6df894f23441bcc
Manual expiry (UTC) Not granted
CI deadline (UTC) Not granted
Workflow https://github.com/Agenta-AI/agenta/actions/runs/35916083444

Dependent tests finished. Post /preview for manual review. Environment absence verified; retained storage requires separate provider accounting.

Post /preview as a new comment to request a one-hour preview. Repeating it while active does not extend the hour.
Preview data is disposable. Expiry is enforced by the next successful cleanup sweep, not an exact timer.
Environment deletion stops its compute. Provider-retained storage and permanent services may still be billed.

@mmabrouk

Copy link
Copy Markdown
Member Author

🤖 The AI agent says: @coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

@mmabrouk I’ll review pull request #7111.

✅ 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 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 9240e24f-ce56-4de8-b599-7415f17256eb

📥 Commits

Reviewing files that changed from the base of the PR and between 09c76da and eee2219.

📒 Files selected for processing (4)
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/superseded-turn.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/superseded-turn.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

@mmabrouk
mmabrouk merged commit e8f7bef into release/v0.121.0 Sep 23, 2026
43 of 44 checks passed
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