Multiple-choice ask_user silently renders as a free-text prompt when trailing tool-call parameters leak into the preceding string argument
还没有人认领这个 Issue。
- 主要语言
- Shell
- 星标
- 11.2k
- 派生
- 1.9k
- 平均合并
- 14 小时 16 分钟
- 30 天内合并 PR
- 6
描述
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_userwith achoicesarray 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.jsonlexcerpts 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.
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
首先跟踪生成原始 tool.execution_start 事件的参数解析器,并使用格式错误的 question 和 -B 示例作为复现输入。检查解码后的字符串值中的尾随参数标记是如何处理的。完成标准是:格式错误的部分解析不再在丢弃参数的情况下静默执行,并且解析器覆盖了报告中的这些形式。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- shell
- 领域
- cli, tooling
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 45/100