Skip to content

fix(runner): stop replaying the current prompt as a prior turn#5489

Open
mmabrouk wants to merge 1 commit into
feat/sessions-last-message-onlyfrom
fix/sessions-no-duplicate-prompt
Open

fix(runner): stop replaying the current prompt as a prior turn#5489
mmabrouk wants to merge 1 commit into
feat/sessions-last-message-onlyfrom
fix/sessions-no-duplicate-prompt

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

The problem

Every first turn of every conversation sent the user's prompt to the model twice.

A one-line probe went in as "Reply with exactly: PONG" and reached the model as:

[{"role": "user", "content": "Reply with exactly: PONG"},
 {"role": "user", "content": "Reply with exactly: PONG"}]

The runner persists the inbound user message before it starts the engine (server.ts), and acquiring a sandbox takes seconds, so by the time reconstruction reads the record log the current turn is already in it. Reconstruction returned that record as a prior turn, and the inbound message was then appended on top of it.

Two things made this easy to miss. It is not gated by the frontend flag: a first turn always carries exactly one message, so the messages.length > 1 guard never blocked it, and it reproduced on 9 out of 9 first turns with the frontend flag off. And a duplicated prompt still produces a correct-looking answer, so every journey in the QA suite passed while it was happening.

The same guard also let reconstruction run for an empty message array and for a lone assistant message, which CodeRabbit flagged separately.

The fix

Drop the current turn's own records by turn_id before folding, so the log contributes only prior turns regardless of when the write lands.

Replace the message-count guard with a shared carriesMinimalHistory predicate, so the runner and the keep-alive check agree on one definition of "this request carries only its own fresh user turn" instead of each inferring it from a count.

Before / after

Before After
First turn, message count 2 (prompt twice) 1
Turn 3 of a 3-turn chat u, u, a, u, a, u u, a, u, a, u
Duplicated prompts across a QA run 11 turns 0 turns

Testing

Runner unit suite passes with 4 new cases pinning the empty array, the lone assistant message, the current-turn exclusion, and a first turn whose only records are its own. Confirmed live on a deployed stack: QA results.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 24, 2026
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 24, 2026 10:11pm

Request Review

@dosubot dosubot Bot added the Backend label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f697e5c7-29f2-4660-9cce-ebf115f34256

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sessions-no-duplicate-prompt

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.

The runner persists the inbound user message before it starts the engine, and
acquiring a sandbox takes seconds, so by the time reconstruction reads the record
log the current turn is already in it. Reconstruction returned that record as a
prior turn and the inbound message was appended on top, sending the prompt to the
model twice. It reproduced on every first turn of every conversation, including
with the frontend flag off, because a first turn always carries exactly one
message and the `messages.length > 1` guard does not stop it.

Drop the current turn's records by `turn_id` before folding, and replace the
message-count guard with a shared `carriesMinimalHistory` predicate that both this
seam and the keep-alive check can agree on. The count alone also let reconstruction
run for an empty array and for a lone assistant message.

Claude-Session: https://claude.ai/code/session_01KM69J7uHafgciiN5zfG7qR
@mmabrouk
mmabrouk force-pushed the fix/sessions-no-duplicate-prompt branch from 2f4cc5e to b652316 Compare July 24, 2026 22:10
@mmabrouk
mmabrouk changed the base branch from fix/sessions-flags-wiring to feat/sessions-last-message-only July 24, 2026 22:10
@mmabrouk
mmabrouk requested a review from ardaerzin July 24, 2026 22:10
@github-actions

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Image tag pr-5489-53428a2
Status Failed
Logs View workflow run
Updated at 2026-07-24T22:29:09.072Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant