Describe the bug
An ask_user call that supplies choices sometimes renders as a plain free-text prompt ("Type your answer…") instead of the usual clickable option buttons — and the options themselves show up as a literal JSON array printed at the end of the question text.
The cause is upstream of any UI: the assistant emits Anthropic-style <parameter name="…"> markup inside a string argument, and the argument parser recovers only the first parameter. Every trailing parameter is silently absorbed into that first parameter's string value. Unlike #3765 (where the whole <invoke> block leaks as text and the tool never runs), here the tool call does execute — just with silently truncated arguments, so nothing surfaces as an error.
Verbatim from the session transcript (tool.execution_start), reformatted only for line breaks:
{
"toolName": "ask_user",
"arguments": {
"question": "…Tests 244 → 247 passing, typecheck clean. Commit it?</question>\n<parameter name=\"choices\">[\"Commit (Recommended)\", \"Edit message\", \"Skip repo\", \"Cancel\"]"
}
}
arguments has exactly one key. choices never arrives, so the client correctly renders a free-text prompt — it has no options to draw.
It is not ask_user-specific. The same shape corrupts ordinary tools, where the damage is worse because the call runs with wrong arguments and nobody notices. A grep call that lost its -n:
{ "-B": "30\">\n<parameter name=\"-n\">true", "output_mode": "content", "pattern": "latched" }
Affected version
Engine 1.0.84-4 (running inside the GitHub Copilot desktop app). Standalone CLI on PATH is 1.0.81-12. Occurrences in my logs span 2026-08-18 → 2026-09-11, so this is not new to one release.
Steps to reproduce the behavior
Not deterministic on demand — it depends on model output. What reliably produces it:
- Run a long session on
claude-opus-5.
- Have the agent call
ask_user with a choices array and a long question (every affected call was 643–944 characters; nothing shorter ever broke).
- Once one malformed call lands, every subsequent tool call of that kind in the session is malformed too — the model copies its own bad output from the transcript.
- Run
/compact. The bad exemplar leaves the context window and calls go back to normal.
Step 3–4 is the strongest signal and is fully reproducible from logs — see the investigation block.
Expected behavior
Either the model never emits parameter markup inside a string value, or — more robustly — the argument parser refuses to accept a partial parse. A string value containing </param> / <parameter name="…"> is unambiguously a serialization failure, and the trailing parameters are trivially recoverable from it. Today it silently succeeds with dropped arguments, which is the worst of the three options.
Additional context
Suggested labels (I can't set them): area:tools, type: Bug.
Related: #3765 — same root family (parameter markup leaking), different failure mode: there the call is never executed; here it executes with silently truncated arguments.
Environment
Engine version 1.0.84-4 (GitHub Copilot desktop app)
CLI on PATH 1.0.81-12
OS Microsoft Windows NT 10.0.26200.0
CPU arch AMD64
Shell PowerShell 7
Surface desktop app, project session
Model claude-opus-5 (long-context tier)
Frequency
Measured, not estimated — scanned every tool.execution_start event in my local session store:
| Metric |
Value |
| Tool calls scanned |
145,260 |
| Calls with leaked parameter markup |
18 (0.012%) |
On claude-opus-5 |
17 |
| On all other models combined |
1 (claude-opus-4.8) |
Tools affected: ask_user ×12, grep ×2, an ADO MCP tool ×2, a Bluebird MCP tool ×1, create ×1.
The global rate is low but misleading — it is heavily clustered. Within an affected session the rate is effectively 100% until a compaction clears it.
Extensions
Not tried with extensions disabled. Canvas extensions were loaded, but they cannot plausibly be involved: the corruption is present in the raw tool.execution_start event, before any client renders it.
Diagnostics available on request
- Redacted
events.jsonl excerpts for both affected sessions
- The scan script used to produce the frequency table
In-depth investigation
The corruption is already present in the raw event, before anything renders it. The tool.execution_start record in the local session store shows arguments carrying a single question key. No client can draw option buttons from that — there are no options in the payload. So this is not a UI bug, and it will reproduce on any surface.
Why it's easy to miss. The stray </question> and <parameter name="choices"> don't show up on screen — only the bare ["Commit (Recommended)", …] array does. That makes it read as a formatting quirk rather than a dropped argument, which is why it took a log scan to find.
Contagion, bounded by compaction. This is the part I'd most like a maintainer to look at, because it turns a rare glitch into a session-wide outage.
Session A — 30 ask_user calls, all claude-opus-5:
calls 1–6 ok
← 5 compactions in 31 minutes (heavy context pressure)
calls 7–11 MALFORMED (contiguous, ~26 h, no compaction in between)
← compaction
calls 12–30 ok (19 consecutive clean calls)
The malformed run is bounded exactly by compaction events on both sides.
Session B — 7 ask_user calls, claude-opus-5, zero compactions for the life of the session (74% of a 1M context window): 7 of 7 malformed, starting with the very first one.
Reading: once one malformed call is in the context window it acts as a few-shot exemplar and the model reproduces its own broken shape indefinitely. Compaction rewrites the context, the exemplar disappears, and output returns to normal. Two sessions is a small sample, so I'd call this a strong inference rather than proven — but it matches both cases exactly and gives users a workaround (/compact, or switch model).
The existing guard isn't holding. The harness system prompt already carries a mitigation for this class — "when calling a tool whose parameter is an object, emit a real JSON object for that parameter. Never put XML or angle-bracket markup inside string values of a tool call." That instruction was in context for every one of the 18 malformed calls. A prompt-level guard can't win against the model's own prior output sitting in the same window; this needs a parser-level check.
Suggested fix, in priority order:
- Reject partial parses. If a decoded string argument contains
</…> or <parameter name=, treat the tool call as malformed rather than passing along truncated arguments. Silent success is the real defect here.
- Recover them.
</question>\n<parameter name="choices">["a","b"] is unambiguously parseable back into the intended arguments. A tolerant post-parse pass would fix the user-visible symptom outright.
- Break the contagion. If a malformed call is detected, avoid replaying its raw text into subsequent context — otherwise one bad call poisons the rest of the session.
Describe the bug
An
ask_usercall that supplieschoicessometimes renders as a plain free-text prompt ("Type your answer…") instead of the usual clickable option buttons — and the options themselves show up as a literal JSON array printed at the end of the question text.The cause is upstream of any UI: the assistant emits Anthropic-style
<parameter name="…">markup inside a string argument, and the argument parser recovers only the first parameter. Every trailing parameter is silently absorbed into that first parameter's string value. Unlike #3765 (where the whole<invoke>block leaks as text and the tool never runs), here the tool call does execute — just with silently truncated arguments, so nothing surfaces as an error.Verbatim from the session transcript (
tool.execution_start), reformatted only for line breaks:{ "toolName": "ask_user", "arguments": { "question": "…Tests 244 → 247 passing, typecheck clean. Commit it?</question>\n<parameter name=\"choices\">[\"Commit (Recommended)\", \"Edit message\", \"Skip repo\", \"Cancel\"]" } }argumentshas exactly one key.choicesnever arrives, so the client correctly renders a free-text prompt — it has no options to draw.It is not
ask_user-specific. The same shape corrupts ordinary tools, where the damage is worse because the call runs with wrong arguments and nobody notices. Agrepcall that lost its-n:{ "-B": "30\">\n<parameter name=\"-n\">true", "output_mode": "content", "pattern": "latched" }Affected version
Engine 1.0.84-4 (running inside the GitHub Copilot desktop app). Standalone CLI on PATH is 1.0.81-12. Occurrences in my logs span 2026-08-18 → 2026-09-11, so this is not new to one release.
Steps to reproduce the behavior
Not deterministic on demand — it depends on model output. What reliably produces it:
claude-opus-5.ask_userwith achoicesarray and a long question (every affected call was 643–944 characters; nothing shorter ever broke)./compact. The bad exemplar leaves the context window and calls go back to normal.Step 3–4 is the strongest signal and is fully reproducible from logs — see the investigation block.
Expected behavior
Either the model never emits parameter markup inside a string value, or — more robustly — the argument parser refuses to accept a partial parse. A string value containing
</param>/<parameter name="…">is unambiguously a serialization failure, and the trailing parameters are trivially recoverable from it. Today it silently succeeds with dropped arguments, which is the worst of the three options.Additional context
Suggested labels (I can't set them):
area:tools,type: Bug.Related: #3765 — same root family (parameter markup leaking), different failure mode: there the call is never executed; here it executes with silently truncated arguments.
Environment
Frequency
Measured, not estimated — scanned every
tool.execution_startevent in my local session store:claude-opus-5claude-opus-4.8)Tools affected:
ask_user×12,grep×2, an ADO MCP tool ×2, a Bluebird MCP tool ×1,create×1.The global rate is low but misleading — it is heavily clustered. Within an affected session the rate is effectively 100% until a compaction clears it.
Extensions
Not tried with extensions disabled. Canvas extensions were loaded, but they cannot plausibly be involved: the corruption is present in the raw
tool.execution_startevent, before any client renders it.Diagnostics available on request
events.jsonlexcerpts for both affected sessionsIn-depth investigation
The corruption is already present in the raw event, before anything renders it. The
tool.execution_startrecord in the local session store showsargumentscarrying a singlequestionkey. No client can draw option buttons from that — there are no options in the payload. So this is not a UI bug, and it will reproduce on any surface.Why it's easy to miss. The stray
</question>and<parameter name="choices">don't show up on screen — only the bare["Commit (Recommended)", …]array does. That makes it read as a formatting quirk rather than a dropped argument, which is why it took a log scan to find.Contagion, bounded by compaction. This is the part I'd most like a maintainer to look at, because it turns a rare glitch into a session-wide outage.
Session A — 30
ask_usercalls, allclaude-opus-5:The malformed run is bounded exactly by compaction events on both sides.
Session B — 7
ask_usercalls,claude-opus-5, zero compactions for the life of the session (74% of a 1M context window): 7 of 7 malformed, starting with the very first one.Reading: once one malformed call is in the context window it acts as a few-shot exemplar and the model reproduces its own broken shape indefinitely. Compaction rewrites the context, the exemplar disappears, and output returns to normal. Two sessions is a small sample, so I'd call this a strong inference rather than proven — but it matches both cases exactly and gives users a workaround (
/compact, or switch model).The existing guard isn't holding. The harness system prompt already carries a mitigation for this class — "when calling a tool whose parameter is an object, emit a real JSON object for that parameter. Never put XML or angle-bracket markup inside string values of a tool call." That instruction was in context for every one of the 18 malformed calls. A prompt-level guard can't win against the model's own prior output sitting in the same window; this needs a parser-level check.
Suggested fix, in priority order:
</…>or<parameter name=, treat the tool call as malformed rather than passing along truncated arguments. Silent success is the real defect here.</question>\n<parameter name="choices">["a","b"]is unambiguously parseable back into the intended arguments. A tolerant post-parse pass would fix the user-visible symptom outright.