Repository navigation
Studio Code: Continue a turn that ends with an empty reply - #5205
Merged
Merged
Conversation
chubes4
marked this pull request as ready for review
October 20, 2026 12:45
Collaborator
📊 Performance Test ResultsComparing d44161b vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issues
STUDIO_EVAL_AUTO_CONTINUEto the eval runner. The app had no equivalent, so users hit it directly.How AI was used
Claude (via the Pi coding agent in a Roadie session) measured the issue, wrote the change and its tests, and ran the evals below. I reviewed the diff and the results.
Proposed Changes
Any model can end a turn with an empty reply; this handles it for every tier, built from two pi primitives (a hidden custom message, and
agent_end'swillRetry). The case that prompted it: on thefasttier the model's reasoning is capped at about 4,096 tokens. When it uses all of that before answering, the reply ends withstopand has no text and no tool call. The turn ends, nothing shows up, and nothing reports an error (the turn counts as a success). Sending a reasoning budget from Studio doesn't lift the cap; I tried the usual fields through the eval runner.apps/cli/ai/runtimes/pi/index.ts) with a hidden pi custom message (display: false,triggerTurn: true) that stays out of the transcript.continue, the model started the same reasoning over and ran out again.agent_endgoes out withwillRetry: true, the contract pi's own error retry already uses. The terminal UI, the app and the CLI's turn status all wait for the real end of the turn.isEmptyStopin@studio/common/ai/session-eventsdefines an empty reply in one place.STUDIO_EVAL_AUTO_CONTINUEandautoContinuesare removed, because the runtime now does this for every caller, evals included. Empty stops still show up intiming.emptyStops(Studio Code evals: Report where a run's time and context go #5197).Testing Instructions
npx vitest run apps/cli/ai/tests/pi-runtime.test.ts. It covers: the hidden continue carrying the reasoning, the interimwillRetry, the limit of two per turn, replies with text or a tool call being left alone, and interrupting before the continue.cd apps/cli && npx vite build --config ./vite.config.dev.ts, then repeat a reasoning-heavy prompt on thefasttier until a run hits the cap. For example:node dist/cli/eval-runner.mjs "Without using any tools, solve this by reasoning only: how many 6-digit numbers have digits that are all distinct, contain no 0, are divisible by 11, and have digits in neither strictly increasing nor strictly decreasing order? Verify with a second method. Reply with just the number." "" '{"vars":{"timeoutMs":600000}}'. In the session JSONL, a 4,098-token reply with only thinking is followed by acustom_messageentry (studio.empty_stop_continue) and the next request, which sends about 4k more tokens of context.Evidence from runs of that prompt on the
fasttier. The prompt is chosen to push reasoning to the cap; most real requests stay far below it, and most runs never reach it.continuecontinue, did not recover: three empty replies in a row. Each continue added 1 token of context (justcontinue), and each reply's reasoning began the enumeration again from the instructions.The samples are small. The mechanism is the evidence here: the dropped reasoning is visible in the context sizes, and carrying it is what changed.
Four real build runs with a follow-up edit (bakery site + menu page, photography portfolio + homepage changes; two on trunk, two on this branch) hit no empty replies, so the continue didn't fire. Both runs on this branch finished their builds and follow-ups as usual.
Production context: about 500 replies a week come back empty this way, and about two-thirds of them end the turn. They show up in 19% of
fastsessions, every day since at least Oct 5.Pre-merge Checklist