Repository navigation
Studio Code: Continue a turn whose tool call leaked into the reply as DSML markup - #5346
Merged
Merged
Conversation
…nue instead of a second path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as a tool call Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
fastturn showed</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>as reply text and then the agent stopped. The same kind of stop showed up repeatedly during a support rotation.How AI was used in this PR
Indent (Claude) traced the report to the runtime and wrote the first version; Claude Code then slimmed it down to reuse #5205's continue. Both ran the tests, typecheck and lint, and Claude Code checked it end to end against the real proxy with a fault-injecting proxy (below). No evals were run.
Proposed Changes
The
fasttier runs on DeepSeek, which writes tool calls in its own DSML markup. The upstream that serves it usually turns that markup into structured tool calls, but sometimes part of it comes back as reply text instead. The tool never runs, the user sees raw<|DSML|…>tags, and since the reply has text and no tool call, the agent just stops.With this change, leaked markup is removed from the final reply, so it never shows in the transcript, the saved session or the next request. When the reply lost its only tool call, the text around the leaked call goes too: the reply is then empty, and the agent picks the turn back up the same way it already does after an empty reply (#5205), from its own reasoning. A reply that still has a real tool call is only cleaned, since the turn keeps going anyway.
The call isn't rebuilt from the leaked text: in the reported case only the closing tags came through, so the tool name and arguments were already gone.
Trade-offs: the raw tags can still flash while the reply streams, since only the final message is cleaned, and the short lead-in a model writes before a tool call ("Let me check the file.") is dropped along with the leaked call. The real fix belongs in the upstream's tool parser; this keeps sessions going until then, including on providers we don't control.
Testing Instructions
npx vitest run apps/cli/ai/runtimes/pi/tests/leaked-tool-call-markup.test.tsWPCOM_AI_PROXY_BASE_URLat a local server that forwards to the wpcom AI proxy, but rewrites one streamed reply so its tool call comes back as DSML text (the call's deltas removed, the tags added as content,finish_reasonset tostopor left attool_calls). Then run afastturn that needs two tool calls throughapps/cli/dist/cli/eval-runner.mjs. Expected: the session JSONL has an empty assistant reply, then astudio.empty_stop_continuecustom message, then the same tool call made for real, and no|DSML|anywhere. Six runs passed this way: the leak on the first or the second call, closing tags only or a full call, and both finish reasons.Pre-merge Checklist
🤖 Generated with Claude Code