fix(runner): stop replaying the current prompt as a prior turn - #5489
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
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
2f4cc5e to
b652316
Compare
Railway Preview Environment
|
fix(runner): keep the warm session when the client sends a minimal history
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 > 1guard 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_idbefore folding, so the log contributes only prior turns regardless of when the write lands.Replace the message-count guard with a shared
carriesMinimalHistorypredicate, 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
u, u, a, u, a, uu, a, u, a, uTesting
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.