fix(core): catch content-only thinking-tag leaks on all OpenAI-compatible providers - #8818
Conversation
…ible providers Production captures (issue #6666) show hybrid-thinking models occasionally bypassing the reasoning channel and emitting their thinking as literal <think>/<thinking> text inside content. The content-only leak fallback only covered the DashScope provider, and its 128-char candidate cap released confirmed opening tags mid-stream, so real-world leaks (always longer than the cap, typically unclosed) still reached users. - Enable contentOnlyThinkingTagLeaks on DefaultOpenAICompatibleProvider so every OpenAI-compatible endpoint gets the conservative fallback (gated to turns that start with a thinking tag and carry no structured reasoning or prior visible content); drop the now-redundant DashScope override. - Classify an opening tag followed by content with no balancing closing tag as an unclosed thinking block: held mid-stream, rejected as PROTOCOL_TAG_LEAK at stream end. Whitespace-only tails stay undecided. - Exempt confirmed opening tags with real content from the length-cap release so long unclosed blocks are rejected instead of leaked. Adds regression tests replaying the sanitized production shape (red on the previous code) plus a control proving the provider gate.
…throw Review follow-up: state honestly that a legitimate balanced literal longer than the candidate cap whose closing tag has not arrived yet is rejected along with real leaks (indistinguishable at that point), and add a regression test for the mid-stream fail-closed throw on over-cap confirmed opening tags.
|
Updated the Reviewer Test Plan to state the actual four added checks in both English and Chinese. No code changed and no actionable review thread or conversation finding exists. Exact-head CI, triage, and automatic review are running. 中文摘要已把 Reviewer Test Plan 的中英文新增检查数量修正为实际的 4 项。未改代码,当前没有可执行的 review thread 或普通评论 finding;exact-head CI、triage 和自动 review 正在运行。 |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. Issue #6666 documented qwen3-class models emitting literal thinking tags into Direction: aligned. The defense already exists and proved out on one vendor; promoting it to default-provider behavior and deleting the now-redundant vendor override is the natural completion of that rollout, not a new mechanism. CHANGELOG has no direct reference to this fallback, but the area (streaming correctness on OpenAI-compatible endpoints) is squarely core. Size: core paths touched ( Approach: scope feels right. Moving the option to the default provider alone would not have fixed the observed shape — the classifier still released unclosed long blocks — so the two converter changes earn their place. Every edit serves the stated goal; no drive-by changes. One trade-off reviewers should keep in mind: once a candidate commits to a confirmed opening tag with content, the length cap no longer releases it, so a legitimate balanced literal longer than the cap whose closing tag has not arrived yet is rejected along with real leaks (turn retried). The PR documents this, and at that point the two cases are genuinely indistinguishable. Risk: Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测,非理论性问题。#6666 记录了 qwen3 系列模型把字面 thinking 标签写进 方向:对齐。防御机制已存在并在单一厂商验证过;提升为默认 provider 行为并删除冗余的厂商覆写是该推广的自然收尾,不是新机制。CHANGELOG 没有对该兜底的直接引用,但流式正确性属于核心领域。 规模:触及核心路径( 方案:范围合理。仅把选项移到默认 provider 不足以修复观测到的形态——分类器仍会放行未闭合的长块——因此两处 converter 改动是必要的。diff 中每处编辑都服务于既定目标,无顺手改动。一个值得评审者留意的取舍:候选一旦确认开标签且携带内容,长度上限不再放行它,因此长于上限且闭标签未到达的合法成对字面量会与真实泄露一起被拒绝(回合重试)。PR 已注明该取舍,且此刻两者确实不可区分。 风险: 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
|
触发条件:任意 OpenAI 兼容端点( 影响:这类 turn 每次都在流中途抛 修复方向:对 |
|
Closeout for exact head
中文摘要当前无活跃 thread,适用的 Qwen Code CI 已通过;自动评审此前超时且无 finding,本轮已 failed-only 重跑,没有为超时修改产品代码。重跑仍在进行。 |
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
中文说明
已审查。 建议见行内评论。
— glm-5.2 via Qwen Code /review (v0.21.8)
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Test Plan (not a blocker): 773 tests pass — this review observed 19613, 1124, 18665, 1481, 486, 2974, 454 passed.
中文说明
已审查。 建议见行内评论。 Test Plan(非阻断):773 tests pass — this review observed 19613, 1124, 18665, 1481, 486, 2974, 454 passed。
— deepseek-v4-flash via Qwen Code /review (v0.21.8)
|
No code changed. The closed 中文摘要未修改代码。闭合 |
|
Qwen Code review timed out. Qwen review timed out after 10800 seconds (of the 180-minute budget). For large PRs, retry with a longer timeout by commenting: |
|
No code changed. The no-angle-format suggestion does not match the parser or #6666, and changing MiniMax is an explicit PR non-goal; both threads were closed. The reproduced closed-tag provider-default behavior remains human-gated. Automatic review attempt 3 is in progress and reaches the retry cap for this fingerprint. 中文摘要未修改代码。无尖括号格式的建议与实际 parser 和 #6666 不符,MiniMax 变更又是 PR 明确的非目标,两条 thread 已关闭。已复现的闭合标签 default-provider 行为继续 human-gated。自动 review 第 3 次 attempt 正在运行,已达到该指纹的重试上限。 |
|
Closed the remaining thinking-tag finding at Verified: focused provider/converter tests (246/246), core typecheck, and core build. Intentionally unchanged: the MiniMax-specific override and model-side behavior. The PR body now reflects the accepted parsing trade-off. The review thread is resolved and @wenshao has been re-requested. CI is still running on the exact head. 中文摘要已在 已验证 provider/converter 聚焦测试 246/246、core typecheck 和 core build。MiniMax 专用覆写及模型侧行为未改。PR 描述已同步接受后的解析取舍;thread 已 resolve,并重新请求 @wenshao 评审。exact head CI 仍在运行。 |
qqqys
left a comment
There was a problem hiding this comment.
[Critical] 本 PR 导致 Test (ubuntu-latest, Node 22.x) 红灯,阻塞合并。
位置:packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts:114-118
(未随本 PR 更新),对应改动是 provider/dashscope.ts 删除了 getResponseParsingOptions() 覆写、
provider/default.ts:127 新增了返回 { taggedThinkingTags: true, contentOnlyThinkingTagLeaks: true } 的基类实现。
触发条件:npm run test:ci -w @qwen-code/qwen-code-core。DashScope provider 现在继承基类实现,
返回值多了 taggedThinkingTags: true,而该测试用的是 toEqual 精确匹配。
影响:head 6a546d5d 上 CI 已实际失败(run 31344854162):
FAIL src/core/openaiContentGenerator/provider/dashscope.test.ts > DashScopeOpenAICompatibleProvider > enables content-only thinking-tag leak detection
AssertionError: expected { taggedThinkingTags: true, …(1) } to deeply equal { contentOnlyThinkingTagLeaks: true }
{
"contentOnlyThinkingTagLeaks": true,
+ "taggedThinkingTags": true,
}
Test Files 1 failed | 572 passed | 1 skipped (574)
上一条说明里的 “focused provider/converter tests (246/246)” 未覆盖 dashscope.test.ts,所以本地没暴露。
修复方向:把该断言更新为继承后的实际值
({ taggedThinkingTags: true, contentOnlyThinkingTagLeaks: true }),
并把用例名/意图改为“继承默认解析选项”;若只想校验单个开关,改用 toMatchObject。
… removal The vendor-specific getResponseParsingOptions override was removed in the parent commit so DashScope inherits the default provider's options, which now include taggedThinkingTags alongside contentOnlyThinkingTagLeaks.
wenshao
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): The change under review: PR 8818 moves the content-only t...: converter.test.ts execution — worktree node_modules is a symlink to review-pr-8815's node_modules, which lacks ajv/dist/2020.js, so the converter suite cannot l…; The change under review: PR 8818 moves the content-only t...: full core suite run — dashscope.test.ts already fails empirically (Finding 1); did not run remaining files due to the same ajv resolution issue; The change under review: PR 8818 moves the content-only t...: end-to-end streaming run to observe Finding 2's user-visible deferral — same environment limitation; mechanism verified by direct code reading; The change under review: PR 8818 moves the content-only t...: could not execute converter.test.ts (and by extension the full openaiContentGenerator suite) in this worktree — collection aborts with Failed to load url ajv…; The change under review: PR 8818 moves the content-only t...: converter.test.ts and minimax.test.ts could not be executed in this worktree — module collection fails on a pre-existing environment issue ( ajv/dist/2020.j…, and 5 more.
Test Plan (not a blocker): 773 tests pass — this review observed 19573, 1124 passed.
中文说明
未探索到全部深度(达到工具调用预算):The change under review: PR 8818 moves the content-only t...:converter.test.ts execution — worktree node_modules is a symlink to review-pr-8815's node_modules, which lacks ajv/dist/2020.js, so the converter suite cannot l…;The change under review: PR 8818 moves the content-only t...:full core suite run — dashscope.test.ts already fails empirically (Finding 1); did not run remaining files due to the same ajv resolution issue;The change under review: PR 8818 moves the content-only t...:end-to-end streaming run to observe Finding 2's user-visible deferral — same environment limitation; mechanism verified by direct code reading;The change under review: PR 8818 moves the content-only t...:could not execute converter.test.ts (and by extension the full openaiContentGenerator suite) in this worktree — collection aborts with Failed to load url ajv…;The change under review: PR 8818 moves the content-only t...:converter.test.ts and minimax.test.ts could not be executed in this worktree — module collection fails on a pre-existing environment issue ( ajv/dist/2020.j…,另有 5 条。
Test Plan(非阻断):773 tests pass — this review observed 19573, 1124 passed。
— DeepSeek/deepseek-v4-flash via Qwen Code /review (v0.21.8)
|
Changed on exact head
Verified: converter and tagged-parser tests 247/247; provider tests 144/144; core package build and typecheck; ESLint; Prettier; Intentionally not changed: Pending: exact-head Ubuntu CI is in progress and automatic review is waiting. All six latest review threads are resolved; the existing reviewer requests remain active. 中文摘要已在 |
原问题已在 8b9a81a 修复:commit dd4ddb8 把 packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts:114-118 的 toEqual 断言补上了 taggedThinkingTags: true,与 provider/default.ts:127 返回的 { taggedThinkingTags: true, contentOnlyThinkingTagLeaks: true } 一致;当前 head 上 Test (ubuntu-latest, Node 22.x) 已通过(job 93352819298)。解除本条 CHANGES_REQUESTED,不代表对本 PR 的整体批准。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): PR #8818 (QwenLM/qwen-code) extends the content-only thin...: full openaiContentGenerator suites (converter.test.ts, pipeline.test.ts, provider tests) not run end-to-end — only the targeted headline test and my probe.; PR #8818 (QwenLM/qwen-code) extends the content-only thin...: non-streaming consumers of convertOpenAIResponseToGemini (e.g. title/summary generation) not traced for the newly extended tagged parsing — assessed as specul….
Not reviewed: reverse audit — stopped before round 4 by the review time budget.
Test Plan (not a blocker): 773 tests pass — this review observed 19562, 1172, 18757, 1501, 481, 2997, 459 passed.
中文说明
未探索到全部深度(达到工具调用预算):PR #8818 (QwenLM/qwen-code) extends the content-only thin...:full openaiContentGenerator suites (converter.test.ts, pipeline.test.ts, provider tests) not run end-to-end — only the targeted headline test and my probe.;PR #8818 (QwenLM/qwen-code) extends the content-only thin...:non-streaming consumers of convertOpenAIResponseToGemini (e.g. title/summary generation) not traced for the newly extended tagged parsing — assessed as specul…。
未审查:反向审计——评审时间预算不足,未能开始第 4 轮。
Test Plan(非阻断):773 tests pass — this review observed 19562, 1172, 18757, 1501, 481, 2997, 459 passed。
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
@qwen-code /verify |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 62 passed · 0 failed · 62 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:62 通过 · 0 失败 · 62 总计 Verification reportVerification report — PR 8818Verdict: 中文摘要
Central claim + A/BCentral claim: the content-only thinking-tag leak defense now covers every OpenAI-compatible endpoint and catches the recorded production shape — unclosed Secondary claims: (1) no false positives — balanced literal tags stay visible, Harness
All 24 scripted expectations encoded in Reviewer Test Plan walkthrough
Findings (non-blocking)
Mutation matrixWitness:
No surviving mutant among the guards the PR introduced. Sibling sweep + scaling ladderWitness:
Targeted gates
Not covered
MethodologyEnvironment: Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): PR #8818 (QwenLM/qwen-code) extends the content-only thin...: did not run ESLint or the full package test suite (only the directly affected files).; PR #8818 (QwenLM/qwen-code) extends the content-only thin...: did not exercise live-provider E2E behavior (no network verification of real DashScope/MiniMax streams).; PR #8818 (QwenLM/qwen-code) extends the content-only thin...: did not execute the test suite (trace-based verification only); did not audit every third-party provider's actual wire behavior (vLLM/Ollama claims are from dom…; PR #8818 (QwenLM/qwen-code) extends the content-only thin...: did not run the test suite — all verification is by code trace, including the claim that the replaced long-prefix test reflects an intentional behavior reversal…; PR #8818 (QwenLM/qwen-code) extends the content-only thin...: the vLLM/Ollama content-only-thinking emission scenario in Finding 2 is domain knowledge, not verified against fixtures in this repo..
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
未探索到全部深度(达到工具调用预算):PR #8818 (QwenLM/qwen-code) extends the content-only thin...:did not run ESLint or the full package test suite (only the directly affected files).;PR #8818 (QwenLM/qwen-code) extends the content-only thin...:did not exercise live-provider E2E behavior (no network verification of real DashScope/MiniMax streams).;PR #8818 (QwenLM/qwen-code) extends the content-only thin...:did not execute the test suite (trace-based verification only); did not audit every third-party provider's actual wire behavior (vLLM/Ollama claims are from dom…;PR #8818 (QwenLM/qwen-code) extends the content-only thin...:did not run the test suite — all verification is by code trace, including the claim that the replaced long-prefix test reflects an intentional behavior reversal…;PR #8818 (QwenLM/qwen-code) extends the content-only thin...:the vLLM/Ollama content-only-thinking emission scenario in Finding 2 is domain knowledge, not verified against fixtures in this repo.。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.21.8)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): 本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...: none — all planned checks (provider sibling sweep, read-site audit, classifier trace, test run) completed within budget.; 本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...: none — all planned checks completed within budget.; 本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...: none — no check was left unfinished at the budget ceiling; the stream-lifetime default value was not read, but the guard's existence (which bounds the scenario ….
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
[Critical] R3-1 (converter.ts:1168) — Still stands from round 3; converter.ts is byte-identical since that comment. A leading balanced-but-empty (or whitespace-only) thinking block classifies clean through the early return if (closing && !rest) return 'clean';, is emitted as visible text mid-stream, and latches hasVisibleContent = true (converter.ts:1655 — only ever set, never reset). That permanently short-circuits contentOnlyThinkingState to 'clean' and blocks canStartTagCandidate, so any later unclosed <thinking> block in the same stream skips the defense entirely and ships verbatim — the exact #6666 shape this PR exists to reject. Failure scenario: with the shipped default config ({ contentOnlyThinkingTagLeaks: true }, no tagged parser), a hybrid-thinking stream without reasoning_content sends chunk A = <thinking></thinking>, chunk B = <thinking>\nThe user wants to query the compute resources..., finish 'stop' → no PROTOCOL_TAG_LEAK throw; chunk B reaches the user as raw visible text (the whitespace variant <thinking> \n</thinking> disarms identically — it is the shape this PR's own 'long empty block' test releases). Control: the identical unclosed block without the leading empty block throws PROTOCOL_TAG_LEAK. Probe-verified at this HEAD by three independent agents with a flip control. Already reported at this exact line in round 3 (existing thread), so it is restated here rather than re-posted inline. Fix direction: keep the defense armed across a balanced tag-only prefix — do not take the clean early return while the consumed tag pair is still the whole stream prefix, or do not latch hasVisibleContent for visible text consisting solely of thinking tags; add a regression test (balanced empty block followed by an unclosed block must throw, keeping the existing 'long empty block' preserve case green).
中文说明
未探索到全部深度(达到工具调用预算):本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...:none — all planned checks (provider sibling sweep, read-site audit, classifier trace, test run) completed within budget.;本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...:none — all planned checks completed within budget.;本次评审对象是 PR #8818(QwenLM/qwen-code,第 4 轮评审):把 content-only...:none — no check was left unfinished at the budget ceiling; the stream-lifetime default value was not read, but the guard's existence (which bounds the scenario …。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
[Critical] R3-1 (converter.ts:1168) — Still stands from round 3; converter.ts is byte-identical since that comment. A leading balanced-but-empty (or whitespace-only) thinking block classifies clean through the early return if (closing && !rest) return 'clean';, is emitted as visible text mid-stream, and latches hasVisibleContent = true (converter.ts:1655 — only ever set, never reset). That permanently short-circuits contentOnlyThinkingState to 'clean' and blocks canStartTagCandidate, so any later unclosed <thinking> block in the same stream skips the defense entirely and ships verbatim — the exact #6666 shape this PR exists to reject. Failure scenario: with the shipped default config ({ contentOnlyThinkingTagLeaks: true }, no tagged parser), a hybrid-thinking stream without reasoning_content sends chunk A = <thinking></thinking>, chunk B = <thinking>\nThe user wants to query the compute resources..., finish 'stop' → no PROTOCOL_TAG_LEAK throw; chunk B reaches the user as raw visible text (the whitespace variant <thinking> \n</thinking> disarms identically — it is the shape this PR's own 'long empty block' test releases). Control: the identical unclosed block without the leading empty block throws PROTOCOL_TAG_LEAK. Probe-verified at this HEAD by three independent agents with a flip control. Already reported at this exact line in round 3 (existing thread), so it is restated here rather than re-posted inline. Fix direction: keep the defense armed across a balanced tag-only prefix — do not take the clean early return while the consumed tag pair is still the whole stream prefix, or do not latch hasVisibleContent for visible text consisting solely of thinking tags; add a regression test (balanced empty block followed by an unclosed block must throw, keeping the existing 'long empty block' preserve case green).
— qwen3.8-max via Qwen Code /review (v0.21.8)
|
Closeout: no code change was needed. The two remaining suggestions requested classifier refactoring and a new incremental parser for a hypothetical oversized literal block; neither is a current-head regression and both are outside this focused provider leak fix. Replied and resolved both threads. Applicable exact-head checks are green. 中文摘要无需改代码;剩余两项分别是分类器重构和假设性超长文本的增量解析架构,均超出当前修复范围,已回复并关闭,适用 CI 为绿色。 |
Local real-stack verification (maintainer)Verified this PR end-to-end on a real environment: both the PR head ( Results
Before — merge-base leaks the production-shaped stream to the user: After — same stream on the PR head: leak suppressed, bounded retry recovers a clean answer: After — persistently leaking endpoint: retry budget exhausted, typed error surfaced instead of the leak: After — regression checks: balanced literal tags (mid-content and leading) stay visible; Ledger excerpts (retry judge)Scenario 1 on the PR head — one leaked attempt, one automatic retry: {"seq":1,"scenario":"leakonce","scenarioReq":1,"t":"2026-08-12T04:10:34.933Z","model":"qwen3-mock","stream":true}
{"seq":2,"scenario":"leakonce","scenarioReq":2,"t":"2026-08-12T04:10:37.253Z","model":"qwen3-mock","stream":true}Scenario 2 on the PR head — initial + exactly 2 retries with increasing backoff, then the typed error: {"seq":4,"scenario":"leakforever","scenarioReq":1,"t":"2026-08-12T04:11:11.866Z",...}
{"seq":5,"scenario":"leakforever","scenarioReq":2,"t":"2026-08-12T04:11:14.211Z",...}
{"seq":6,"scenario":"leakforever","scenarioReq":3,"t":"2026-08-12T04:11:18.545Z",...}On the merge-base, scenario 1 produced exactly one request and the raw block was rendered — confirming both bypasses the PR describes (the classifier releasing an opening tag followed by text, and the candidate-cap release) are real on the parent and closed on the head. Focused tests (PR head)
Notes
Conclusion: behaves exactly as described — the content-only defense now applies to generic OpenAI-compatible endpoints, the two real-world bypasses are closed, recovery and fail-closed paths both work, and balanced-literal / 中文版本(Chinese version)本地真实环境验证(维护者)对 PR head( 结果
截图见上方英文部分(改前泄露直出 / 改后重试恢复 / 重试耗尽 fail-closed / 平衡标签与 reasoning 回归)。 台账摘录(重试判据)场景 1(head):1 次泄露尝试 + 1 次自动重试(见上方 JSON)。场景 2(head):初始 + 恰好 2 次重试、退避递增,然后报类型化错误。merge-base 上场景 1 仅 1 次请求且原始块被渲染——证实 PR 描述的两处绕过(分类器放行"开标签+后续文本"、候选上限放行)在父提交上真实存在、在 head 上已封闭。 聚焦测试(PR head)
说明
结论:与描述完全一致——content-only 防御已作用于通用 OpenAI 兼容端点,两处真实绕过已封闭,恢复路径与 fail-closed 路径均工作正常,平衡字面标签与 |
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 143 passed · 0 failed · 143 total 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:143 通过 · 0 失败 · 143 总计 Verification reportVerification report — PR 8818 (follow-up round 2)Verdict: 中文摘要
Previous-finding status (round 1 → round 2)Round 1 verified head
Corrections to the round-1 record
Central claim + A/BCentral claim: the content-only thinking-tag leak defense now covers every OpenAI-compatible endpoint (default-provider option) and catches the recorded production shape — unclosed Secondary claims: (1) no false positives — balanced literals stay visible (now even when the close arrives after the cap), Harnesses (
All 31 scripted expectations in Findings (non-blocking)
Informational (no action needed):
Reviewer Test Plan walkthrough
Mutation matrixWitness:
Vacuity: the central new tests fail with behavioral expected-vs-actual messages under mutation (quoted for M2 above), and the positive control (M7) lands a known-pinned red — so the survivors are measurements, not harness artifacts. Sibling sweep + scaling ladderWitness:
Targeted gates
Not covered
MethodologyEnvironment: Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Code review — I traced the classifier and the stream state machine on this head independently before settling on the diff, and the two converter changes earn their keep: promoting the option to the base provider alone would not have stopped the recorded production shape, because an unclosed block longer than the 128-char candidate cap was still released by the cap. The fix holds a confirmed opening-tag candidate until a balancing close or stream end, classifies an opening tag with a non-whitespace tail and no closing tag as One residual, non-blocking: the earlier review-round finding — a stream that opens with a balanced thinking block emits visible text, latches Testing — CI on the exact head
The central claim is behavioral, so beyond CI two lanes speak to it: a sandboxed 中文说明代码审查 —— 我在下结论前独立追踪了当前 head 上的分类器与流状态机:两处 converter 改动是必要的。仅把选项提升到默认 provider 无法拦住记录在案的生产形态——超过 128 字符候选上限的未闭合块此前仍会被上限放行。修复把已确认的开标签候选持续缓冲到配对闭标签或流结束;开标签后跟非空白内容且无闭标签时,流结束判为 一个非阻塞的遗留点:此前评审轮次的发现——流以成对 thinking 块开头时会输出可见文本并锁存 测试 —— exact head 行为性结论除 CI 外还有两条通道:沙箱 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — observed production bug, minimal well-scoped fix, green exact-head CI, maintainer real-stack verification on the same commit; one pre-existing residual bypass noted as follow-up. This one does what it set out to do. The production captures show raw My one reservation, stated plainly: a stream that begins with a balanced thinking block disarms the defense for any later unclosed block in the same stream (the CI is green on the exact head with no pending runs, and the approving maintainer exercised the real TUI against a generic endpoint on this same commit. Approving, pinned to the reviewed commit. ✅ — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.21.11. |











What this PR does
Extends the content-only thinking-tag leak defense to every OpenAI-compatible endpoint and closes the bypasses that let real-world unclosed leaks through. The fallback is now part of the default provider behavior instead of a single vendor's opt-in, while generic providers do not enable tagged-thinking parsing: properly balanced literal tags remain visible,
reasoning_contentcontinues to stream independently, and the existing bounded protocol-leak retry remains reachable. MiniMax keeps its explicit tagged-thinking parser. The stream classifier treats an opening thinking tag with no balancing close as an unclosed block and no longer releases a confirmed opening tag at the candidate length cap.Why it's needed
Hybrid-thinking models occasionally bypass the reasoning channel and emit their thinking as literal think tags inside the visible content. Issue #6666 reported this for qwen3-class models and was closed as fixed, but the fallback only covered endpoints recognized as one vendor's official hosts, and production captures from a DataWorks DataAgent deployment (4 confirmed sessions, late July – early August 2026, including one after our latest rollout) show the raw tags reaching end users through other OpenAI-compatible endpoints. Replaying one sanitized capture against the current code reproduced the leak exactly: the production turns are unclosed blocks longer than the 128-character candidate cap, and both the classifier and the cap release let them through.
Reviewer Test Plan
How to verify
Run the focused provider and converter tests. On the current head, 144 provider tests and 247 converter/tagged-parser tests pass, including unclosed production-shape detection, retry reachability, immediate
reasoning_contentplus content streaming, balanced literal preservation, MiniMax tagged parsing, and the candidate-cap boundary. The core package build and typecheck also pass.Evidence (Before & After)
N/A (stream-conversion internals, no UI change). On the parent behavior, the production-shaped unclosed stream is emitted visibly; on the current head it raises the typed protocol-leak error and enters the bounded retry path. Focused tests, core package build/typecheck, ESLint, Prettier, and
git diff --checkare clean.Tested on
Environment (optional)
Unit tests only (
npm install --ignore-scripts, Node v22).Risk & Scope
Linked Issues
Reference #6666 (requested reopen with the production evidence in the issue comments).
中文说明
这个 PR 做了什么
将“仅正文 thinking 标签泄露”的防御扩展到所有 OpenAI 兼容端点,并堵住真实未闭合泄露的绕过路径。该兜底从单一厂商专属改为默认 provider 行为,但 generic provider 不启用 tagged-thinking parser:正确平衡的字面标签继续可见,
reasoning_content独立流式输出,已有的有限协议泄露重试保持可达;MiniMax 继续显式启用 tagged parser。流式分类器会把没有配对闭标签的开标签判为未闭合块,且已确认的开标签不再被候选长度上限中途放行。为什么需要
混合思考模型偶发绕过 reasoning 通道、把思考以字面 think 标签写进可见正文。#6666 报告了 qwen3 系列模型的该行为并被标记为已修复,但兜底只覆盖被识别为某厂商官方域名的端点;DataWorks DataAgent 生产部署的 4 个确认会话(2026 年 7 月底至 8 月初,含一例发生在最新推送之后)显示原始标签经由其他 OpenAI 兼容端点直达用户。用一份脱敏生产样本在当前代码上回放,精确复现了泄露:生产泄露形态是长于 128 字符候选上限的未闭合块,分类器和上限放行两处都会放它通过。
评审者测试计划
如何验证
运行聚焦的 provider 与 converter 测试。当前 head 上 144 个 provider 测试和 247 个 converter/tagged-parser 测试通过,覆盖生产形态的未闭合泄露、重试可达性、
reasoning_content与正文即时流式输出、平衡字面标签保留、MiniMax tagged 解析及候选上限边界;core package build 与 typecheck 也通过。证据(前后对比)
N/A(流转换内部逻辑,无 UI 变化)。父行为会把生产形态的未闭合流作为可见文本输出;当前 head 会抛出带类型的协议泄露错误并进入有限重试。聚焦测试、core package build/typecheck、ESLint、Prettier 与
git diff --check均通过。测试环境
环境(可选)
仅单元测试(
npm install --ignore-scripts,Node v22)。风险与范围
关联 Issue
引用 #6666(已在 issue 评论中附生产实证并请求 reopen)。