Skip to content

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

Merged
bekossy merged 5 commits into
feat/sessions-storage-reworkfrom
fix/sessions-no-duplicate-prompt
Jul 27, 2026
Merged

fix(runner): stop replaying the current prompt as a prior turn#5489
bekossy merged 5 commits into
feat/sessions-storage-reworkfrom
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 27, 2026 6:27pm

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: 99343c93-ae4d-4b9b-94da-780bb9a07e5b

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.

mmabrouk added 2 commits July 25, 2026 00:09
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
…story

The keep-alive check fingerprints the prior conversation the client sent and
compares it against what the previous turn stored. A last-message-only client
sends no prior conversation, so the fingerprint is of an empty array and can
never match. Every conversation was evicted to cold on every turn after the
first. Measured on a three-turn run, turn 2 went from 1570ms to 4623ms while the
suite still reported a pass, because a cold turn is still faster than the first.

Skip the history comparison when the request carries only its own fresh user
turn. The session id already binds the request to the conversation; a
minimal-history client simply no longer asserts it, so there is nothing to
compare. Requests that do send a history are still checked exactly as before,
including the approval-resume path, which always sends the full history.

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

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-production-7d7b.up.railway.app/w
Image tag pr-5489-5caed22
Status Failed
Railway logs Open logs
Logs View workflow run
Updated at 2026-07-27T18:47:06.303Z

@bekossy
bekossy changed the base branch from feat/sessions-last-message-only to feat/sessions-storage-rework July 27, 2026 17:55
fix(runner): keep the warm session when the client sends a minimal history
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jul 27, 2026
@bekossy
bekossy merged commit ef768f5 into feat/sessions-storage-rework Jul 27, 2026
34 of 35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants