Skip to content

fix(core): recover outer XML calls with quoted call content - #13579

Merged
yiliang114 merged 38 commits into
mainfrom
codex/issue-13492-outer-call-recovery
Oct 27, 2026
Merged

yiliang114 merged 38 commits into
mainfrom
codex/issue-13492-outer-call-recovery

Conversation

@yiliang114

@yiliang114 yiliang114 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Recover a complete outer XML tool call whose parameter value quotes other calls, while keeping the quoted markup inert. An ambiguous or malformed block is now rejected on its own and the scan resumes past it, so one bad envelope no longer costs the genuine calls beside it.

What this changes

  • Closes the reported gap in XML tool-call recovery drops outer calls containing quoted tool markup #13492: a write_file whose content contains an unfenced example call is recovered exactly once with the complete content, and the quoted call never dispatches.
  • Rejects ambiguity per block instead of per turn. The whole-text opener pre-scan, the unclosed-opener veto and the fence-disagreement pass that used to return no calls for the whole turn are gone; a rejected block resumes at its own next open tag.
  • Reads a depth-paired value's complete extent when the value documents a nested parameter element, instead of stopping at the flat match.
  • Treats HTML comments and CommonMark indented code blocks as documentation, like fences and <example>. A value that quotes a call used to let a commented-out shell command dispatch while stripping its own markup from the visible turn.
  • Rejects a block whose parameter names repeat, because the quote advance can otherwise pull a stray element into the block and silently overwrite an argument.
  • Keeps a value that only mentions a bare closing tag on the flat path, so that block is rejected rather than dispatched with a truncated write.

Reviewer Test Plan

Return a document write whose content quotes a harmless call, followed by an independent control-file read. Expect the write executed once with exact bytes, the quoted call inert, and the read executed.

Then vary the shape: a trailing parameter behind the quoted value, a documented parameter element inside the value, the same shape inside a fence, an <example>, an HTML comment and an indented block, a malformed block before an intact call, and a prose mention of <!-- before a live call. Expect documentation to stay inert and the intact calls to recover.

Evidence

  • packages/core: xml-tool-call-fallback.test.ts 138/138, llm-chat.test.ts 567/567; typecheck, ESLint and Prettier clean on both changed files.
  • 6000-case seeded fuzz over tag/fence/comment fragments: no exception, no hang, max 2.8 ms; recovered: false never changes remainingText.
  • Cost after deleting the whole-turn pre-scan: 1000 malformed openers inside a fence ~1 ms (was ~2.2 s at the reviewed head), 64 KB example body ~13 ms (was ~420 ms).
  • No CLI/tmux end-to-end run this round. The production call site is unchanged; the earlier CLI evidence covered the whole-turn decline, which this version removes.

Risk & Scope

The contract changes from turn-wide decline to per-block rejection. That is what #13492 asks for, and it restores the three witnesses the branch had inverted. It also means a complete call inside a rejected block's unclosed value is recovered again, as on the merge base.

Known limitation: a value that only mentions a bare closing tag, without quoting a complete call, still rejects its block. Accepting it would dispatch the truncated value pinned by preserves malformed blocks instead of dispatching partial calls, so this keeps Refs rather than Fixes.

Performance #13787 stays open: the remaining per-value fence scans are quadratic in the number of value-quoting blocks. Windows and macOS were not run locally.

Linked issues

Refs #13492. Follow-up to #13515.

äļ­æ–‡čŊŽ

åĪ–åą‚č°ƒį”ĻåœĻ参数倞里垕į”Ļå…ķåۃ调į”Ļäū‹åĶ‚ write_file įš„ content 里写䚆äŪĩįĪšäū‹ callæ—ķïžŒįŽ°åœĻ及æĒåĪäļ€æŽĄåĪ–åą‚č°ƒį”Ļ、äŋį•™åیæ•ī contentčĒŦåž•į”Ļįš„č°ƒį”ĻäŋæŒäļšæ•°æŪ参数、example 或å›īæ å―’åąžæœ‰æ­§äđ‰æ—ķ及拒įŧå‡šé—ŪéĒ˜įš„é‚ĢäļŠ block åđķäŧŽåŪƒč‡Šå·ąįš„äļ‹äļ€äļŠ open tag įŧ§įŧ­æ‰Ŧ描äļå†čŪĐæ•īč―ŪåĪąčīĨ。

同æ—ķ把 HTML æģĻ释äļŽ CommonMark įžĐčŋ›äŧĢį å—å―“ä―œæ–‡æĄĢ匚æ­Ī前倞里垕į”Ļ调į”Ļ侚čŪĐčĒŦæģĻé‡ŠæŽ‰įš„ shell å‘―äŧĪčĒŦæ‰§čĄŒïžŒä ‡čŪ°äŧŽåŊč§æ–‡æœŽäļ­æķˆåĪąïž‰ïžŒåđķæ‹’įŧå‚数名重åĪįš„ blockquote advance åŊčƒ―æŠŠæļļįĶŧ元įī æ‹‰čŋ› block åđķčĶ†į›–å‰éĒįš„å‚æ•°ïž‰ã€‚

驌čŊïžšxml-tool-call-fallback.test.ts 138/138、llm-chat.test.ts 567/567äļĪäļŠæ”đåŠĻ文äŧķ typecheck/ESLint/Prettier åđē净6000 äū‹į§å­ fuzz æ— åž‚åļļ、无挂čĩ·ïž›1000 äļŠå›ī栏内į•ļå―Ē opener įšĶ 1msæ­Ī前įšĶ 2.2s64KB example įšĶ 13msæ­Ī前įšĶ 420ms。朎č―ŪæœŠč·‘ CLI/tmux įŦŊ到įŦŊïžŒį”Ÿäš§č°ƒį”Ļį‚đ朊变。

å·ēįŸĨ限åˆķ倞里及提及čĢļįŧ“束标čŪ°ïžˆæēĄæœ‰åž•į”Ļåیæ•ī调į”Ļæ—ķäŧæ‹’įŧčŊĨ block因äļšæŽĨ受åŪƒäžšæīū发čĒŦ preserves malformed blocks instead of dispatching partial calls é’‰ä―įš„æˆŠæ–­å€žïžŒå› æ­Īå°ūæģĻäŋæŒ Refsã€‚æ€§čƒ― #13787 įŧ§įŧ­č·Ÿčŋ›ã€‚

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Oct 7, 2026
@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — view run. See the stage comments in this thread for the result.

✅ Qwen Triage å·ēåŪŒæˆ —— æŸĨįœ‹čŋčĄŒã€‚įŧ“æžœč§æœŽįšŋįĻ‹äļ­įš„各é˜ķæŪĩčŊ„čŪšã€‚

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Local CLI verification: PASS — issue #13492 (2026-10-07)

  • Mode: merge-reference, real noninteractive package CLI in tmux with a dedicated controlled loopback provider. This is runtime/tool execution evidence, not a browser or interactive TUI layout test.
  • Input revision: base 1068240ed7ce3a8e261aa2e4335add8f3efcc876 plus frozen patch SHA-256 ff502d51c6f54d1dd1fff42b38e75f764e7d80e9bb8d9e9e737db82d6433ad41.
  • Runtime: packages/cli/dist/index.js, same built CLI/consumer in both arms. Only the before parser was redirected to the exact baseline source, with native TypeScript stripping. Both arms used the same prompt, provider XML, settings and 190×48 terminal dimensions; separate anonymous workspaces shared one absolute-path alias for identical tool parameters. Final actual runs were at 2026-10-07 06:39 UTC; both exited 0.
  • Configuration: isolated QWEN_HOME/QWEN_RUNTIME_DIR, explicit native write_file/read_file tools, yolo approval and stream-json output. HOME and user configuration were not changed. The final command omitted --bare so the explicit tool configuration applied.
User-visible contract Before After Verdict
Outer write whose content quotes shell-call markup Outer write omitted; no doc.md One native write_file; real success receipt; complete file bytes preserved PASS
Quoted shell call remains data No recovered/executed shell call No recovered/executed shell call PASS
Actual read_file sibling One native read, real control bytes One native read, identical control bytes PASS

The candidate’s actual tool receipt was Successfully created and wrote to new file: <anonymous-fixture>/active/doc.md. The resulting file is exactly the following 110 UTF-8 bytes, with no final newline:

Usage:
<invoke name="run_shell_command"><parameter name="command">printf QUOTED_DATA_ONLY</parameter></invoke>

File SHA-256: e4a0706d7e00ab221d69da64fa79ef06c2bacc17d3f580bcc67af7a83eef2946. Both reads returned ANONYMOUS_CONTROL_BYTES\n, SHA-256 62db81e794c642527d18f648489f664d5e6a320725b4b7f5a9b43cea670c5fb7. The quoted command was harmless test data. Actual CLI tool_use events were before [read_file], after [write_file, read_file]; their corresponding native tool_result events were successful. Provider logs contain 3 HTTP requests per arm; later requests repeat conversation history, so execution counts come from unique CLI tool events rather than counting repeated receipts.

Runtime identity and stability: the hook’s actual resolve/load receipts prove llm-chat.js loaded <verification>/harness/baseline.mjs before (original source SHA-256 a6052efbda59bc1bf27367ac5de28a0c1834b43b1abfb9aa9cfb6af22024a505, stripped module SHA-256 54bab6c70d3b4170514e11d3fde2b9ad30e4fa93f091af87b11cb1cc1be3a3f7) and packages/core/dist/src/core/xml-tool-call-fallback.js after (source SHA-256 6966d44732a4c498516e071eb45656bfee2c9a86bfdb5722a57cda22b3dee335, loaded module SHA-256 5843722999a1183356d103769d4392f5282971904ca65ab1a1749572841ea3c3). CLI entry SHA-256 was 43c7251b6e923a9d112f6a5dd694725fcc1bcdfafd9225ad1c05dbc13981eabf; consumer SHA-256 was b506ef6ee177dc4bd2619b499b599b1b1a1ec301d6e2c821a871e5408b031bc0. All five pinned source/artifact hashes remained identical. The byte-identical candidate was subsequently committed as 01b4eedc8b1d1604236e93a6780e4401dfdfe061; this report pins tested bytes and does not claim a rebuild at that commit.

Regression pin: the two selected changed quoted-call tests—outer invoke and long quoted function value—failed against a temporary resolve alias to the exact baseline parser (2 fail), and passed against the candidate (2 pass). This is separately labeled helper-level evidence; the full 83 parser suite was not repeated. The focused parser/chat consumer suites passed 636 tests (83 parser and 553 consumer). The full build and bundle, core typecheck, changed-file lint/format checks, and diff whitespace check passed separately on the same frozen candidate. Two bounded correctness/security reviews found no verified blocking finding.

Actual sanitized tmux excerpts retain the terminal’s original wrapping:

[before.tmux-full.txt]
esult","tool_use_id":"xml-recovered-0-1791355144739","is_error":false,"content":"ANONYMOUS_CONTROL_BYTES\n"}]}}
[owned runner: actual CLI exited 0]
[after.tmux-full.txt]
esult","tool_use_id":"xml-recovered-0-1791355146480","is_error":false,"content":"Successfully created and wrote to new file: <anonymous-fixture>/active/doc.md."}]}}
esult","tool_use_id":"xml-recovered-1-1791355146480","is_error":false,"content":"ANONYMOUS_CONTROL_BYTES\n"}]}}
[owned runner: actual CLI exited 0]

Setup failures preserved: the primary input used invalid relative native file paths. Its sole fallback corrected absolute paths, but the --bare profile returned a generic tool-unavailable error for write_file. Neither attempt proved native writing; both are retained separately. One final harness configuration correction removed --bare and permitted the same native tools in both arms, producing the successful pair above. The earlier error is not diagnosed as a product allowlist defect.

Evidence and cleanup: result.json, before/after.parser-load.jsonl, before/after.cli-output.log, before/after.tmux-full.txt, before/after.tmux-final.txt, provider-receipts.json, after.doc.md, file-state.json, before/after.regression.json, identity-before/after.json, and cleanup.json are preserved with sanitized identities. All 16 final checks passed. Final 7 owned PIDs exited; both tmux sessions, provider, profiles, workspaces, parser fixture/hook and harness were removed. Earlier setup resources were also cleaned. No source/config/Git/GitHub mutation was performed by this tester. No screenshots were needed because layout/color were not changed. Controlled-provider results do not establish production-model behavior; pre-recovery streaming raw XML visibility is outside this fix and is not asserted absent.

VERDICT: PASS


äļ­æ–‡åیæ•īæŠĨ告朎地 CLI 驌čŊé€ščŋ‡ïžˆ#134922026-10-07

æœŽæŽĄé‡‡į”Ļ merge-reference æĻĄåžïžŒåœĻᜟåŪž tmux äļ­čŋčĄŒå·ē构åŧšįš„ packages/cli/dist/index.jsä―ŋį”Ļį‹ŽįŦ‹ loopback controlled provider。åŪƒéŠŒčŊåŪžé™… CLI įš„æĒåĪã€å·Ĩå…·æ‰§čĄŒåŠč―į›˜įŧ“果äļæ˜Ŋæĩč§ˆå™Ļ或äšĪ䚒垏 TUI åļƒåą€æĩ‹čŊ•。čū“å…Ĩį‰ˆæœŽæ˜Ŋ埚įšŋ 1068240ed7ce3a8e261aa2e4335add8f3efcc876 加å†ŧįŧ“ patch SHA-256 ff502d51c6f54d1dd1fff42b38e75f764e7d80e9bb8d9e9e737db82d6433ad41ïž›äļĪ臂ä―ŋį”Ļåیå…Ļį›ļåŒįš„ built CLI/consumerbefore äŧ…通čŋ‡äļīæ—ķ resolve hook åŠ č――įēūįĄŪ埚įšŋ parser。åŪžé™…čŋčĄŒæ—ķé—īäļš 2026-10-07 06:39 UTCäļĪč‡‚å‡ exit 0。

äļĪ臂 prompt、provider XML、profile settings 和 190×48 įŧˆįŦŊå°šåŊļį›ļ同åŪžé™… workspace 分įĶŧ通čŋ‡åŒäļ€äļŠč‡Šæœ‰įŧåŊđč·Ŋåū„åˆŦ名äŋæŒå·Ĩ具参数文朎äļ€č‡ī。配į―Ūäŧ…ä―ŋį”Ļį‹ŽįŦ‹ QWEN_HOME/QWEN_RUNTIME_DIRæ˜ū垏čŪļåŊåŽŸį”Ÿ write_file/read_fileyolo åŪĄæ‰đ及 stream-json čū“凚。æēĄæœ‰äŋŪæ”đ HOME、į”Ļ户配į―Ūæˆ–éĄđį›Ū配į―Ū最后äļ€č―ŪäļåļĶ --bareä―ŋæ˜ū垏å·Ĩ具配į―Ūį”Ÿæ•ˆã€‚

  • åĪ–åą‚ write_file通čŋ‡ã€‚ before æēĄæœ‰æĒåĪåĪ–åą‚å†™å…Ĩdoc.md äļå­˜åœĻïž›after åŠæ‰§čĄŒäļ€æŽĄ native write_fileæ”ķ到ᜟåŪžåˆ›åŧ𿈐功 receipt文äŧķ内åŪđåیæ•īäŋį•™ quoted shell markup。
  • quoted shell äŋæŒæ•°æŪ通čŋ‡ã€‚ äļĪč‡‚éƒ―æēĄæœ‰æĒåĪæˆ–æ‰§čĄŒ quoted run_shell_command。įĪšäū‹å‘―äŧĪ及æ˜Ŋ无åŪģåŒŋ名数æŪ。
  • åŪžé™… sibling read_file通čŋ‡ã€‚ äļĪč‡‚å„æ‰§čĄŒäļ€æŽĄïžŒå‡čŋ”å›žįœŸåŪž ANONYMOUS_CONTROL_BYTES\n文äŧķå­—čŠ‚åیå…Ļį›ļ同。

after 文äŧķæ­Ģæ˜Ŋä–đäŧĢį å—įš„åیæ•ī 110 äļŠ UTF-8 å­—čŠ‚ïžŒæēĄæœ‰æœŦå°ūæĒčĄŒïžŒSHA-256 äļš e4a0706d7e00ab221d69da64fa79ef06c2bacc17d3f580bcc67af7a83eef2946ïž›control 文äŧķ SHA-256 äļš 62db81e794c642527d18f648489f664d5e6a320725b4b7f5a9b43cea670c5fb7。åŪžé™… CLI tool_use 列čĄĻ before äļš [read_file]after äļš [write_file, read_file]åŊđåš” native tool_result 均成功。provider äļĪ臂各æ”ķ到 3 æŽĄ HTTP čŊ·æą‚后įŧ­čŊ·æą‚䞚重åĪåŽ†åē receipt因æ­Īæ‰§čĄŒæŽĄæ•°æ đæŪå”Ŋäļ€ CLI tool event åˆĪ断äļæŠŠé‡åĪåŽ†åēå―“æˆåĪšæŽĄæ‰§čĄŒã€‚

åŪžé™… hook įš„ resolve/load receipt čŊæ˜Ž before åŠ č――åŸšįšŋ fixtureïžŒåŽŸå§‹æšį  SHA-256 äļš a6052efbda59bc1bf27367ac5de28a0c1834b43b1abfb9aa9cfb6af22024a505native TS stripping 后æĻĄå—äļš 54bab6c70d3b4170514e11d3fde2b9ad30e4fa93f091af87b11cb1cc1be3a3f7ïž›after åŠ č――åŪžé™… built parserïžŒæšį äļš 6966d44732a4c498516e071eb45656bfee2c9a86bfdb5722a57cda22b3dee335æĻĄå—äļš 5843722999a1183356d103769d4392f5282971904ca65ab1a1749572841ea3c3。CLI entry 和 consumer įš„åیæ•ī哈åļŒå·ēåˆ—äšŽč‹ąæ–‡æŪĩ及朚å™Ļįŧ“果䚔äļŠå›šåŪšæšį /äš§į‰Đ哈åļŒå‰åŽå‡æœŠå˜åŒ–。į›ļåŒå­—čŠ‚äđ‹åŽæäšĪäļš 01b4eedc8b1d1604236e93a6780e4401dfdfe061朎æŠĨ告囚åۚåŪžé™…æĩ‹čŊ•å­—čŠ‚ïžŒäļå̰᧰čŊĨ提äšĪé‡æ–°įž–čŊ‘čŋ‡ã€‚

äļĪæĄé€‰åŪšå›žå―’ïžˆåĪ–åą‚ invoke、é•ŋ quoted function value通čŋ‡äļīæ—ķ Vitest resolve alias 指向įēūįĄŪ埚įšŋ parser æ—ķ均åĪąčīĨ指向 candidate æ—ķ均通čŋ‡ã€‚čŋ™æ˜Ŋį‹ŽįŦ‹ helper įš§čīŸ/æ­ĢåŊđį…§ïžŒæēĄæœ‰é‡č·‘åیæ•ī 83 éĄđ parser suite同äļ€å†ŧįŧ“候选å·ē通čŋ‡č§Ģ析å™Ļ/chat 调į”Ļæ–đå…ą 636 éĄđæĩ‹čŊ•83 éĄđč§Ģ析å™Ļ、553 éĄđ调į”Ļæ–đ、åیæ•ī build/bundle、core įąŧ型æĢ€æŸĨ、æ”đåŠĻ文äŧķ lint/栞垏æĢ€æŸĨ及 diff įĐšį™―æĢ€æŸĨ。äļĪč―Ūæœ‰é™æ­ĢįĄŪ性/åŪ‰å…ĻåĪæ ļæēĄæœ‰å‘įŽ°įŧéЌčŊįš„é˜ŧåĄžé—ŪéĒ˜ïž›æœŽč―ŪæēĄæœ‰é‡åĪčŋ™äš›æĢ€æŸĨ。

äŋį•™äš† setup åĪąčīĨ最初į›ļåŊđč·Ŋåū„čĒŦ native å·Ĩ具拒įŧïž›å”Ŋäļ€ fallback ä―ŋį”ĻįŧåŊđč·Ŋåū„后--bare 配į―Ūčŋ”回 write_file įš„é€šį”Ļ tool-unavailable 提įĪšã€‚čŋ™äļĪč―ŪæēĄæœ‰čŊæ˜Ž native 写å…ĨäđŸæēĄæœ‰čĒŦæ ‡ä―œäš§å“ allowlist įžšé™·ã€‚æœ€åŽåŠįš æ­Ģäļ€č―Ūč‡Šæœ‰æĩ‹čŊ•配į―ŪïžšåŽŧ掉 --bareäļĪ臂æ˜ū垏ä―ŋį”Ļį›ļåŒåŽŸį”Ÿå·Ĩ具čŪļåŊéĄđ才åū—到äļŠčŋ°æˆåŠŸé…åŊđ。

åŪžé™… tmux capture、CLI stdout、provider requests/tool receiptsã€č―į›˜æ–‡äŧķ、parser load čšŦäŧ―、äļĪæĄå›žå―’įŧ“果及 source/artifact manifest å·ēč„ąæ•äŋå­˜ïž›ä–đįŧˆįŦŊæ‘˜å―•äŋį•™åŽŸå§‹æĒčĄŒã€‚æœ€įŧˆ 16 éĄđæĢ€æŸĨå…ĻéƒĻ通čŋ‡ïžŒæœ€åŽäļ€č―Ū 7 äļŠč‡Šæœ‰ PID å…ĻéƒĻ退凚äļĪįŧ„ tmux、provider、profiles/workspaces、parser fixture/hook 和 harness å·ē删é™Īäđ‹å‰ setup čĩ„暐äđŸå·ēæļ…į†ã€‚æĩ‹čŊ•䚚员æēĄæœ‰äŋŪæ”đæšį ã€į”Ļ户配į―Ū、Git 或 GitHub。æ­ĪæŽĄäļæķ‰åŠåļƒåą€/éǜč‰ē变化因æ­Īä―ŋį”Ļ文朎 capture åģåŊäļéœ€č́å›ūį‰‡ã€‚controlled provider įš„įŧ“æžœäļäŧĢčĄĻᜟåŪžį”Ÿäš§æĻĄåž‹čĄĻįŽ°ïž›æĒåĪå‰ streaming raw XML åŊč§æ€§äļåąžäšŽæ­ĪäŋŪåĪïžŒäđŸæēĄæœ‰åŪĢį§°å…ķæķˆåĪąã€‚

įŧ“čŪšïžšPASS。

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for the PR — re-run against the new head 5e5e7321a707, the first commit since the maintainer handoff on 2026-10-11.

Template looks good ✓ (all nine headings present)

Problem: observed, not theoretical. #13492 is open and self-reported by you (review/self-reported), and the reproduction history here is unusually strong — including your own end-to-end measurement at the previous head 32acef09969d that returned FAIL because two of three CLI shapes dispatched a documented call the merge base refused. A real defect with a first-party reproduction.

Duplicate check (1-pre): base main is the default branch, and the single linked issue #13492 is OPEN — so no subsumption or not-planned exit applies.

Direction: aligned. This fallback exists for #8003 (models emitting raw XML tool calls instead of the structured array in long contexts), and #13515 correctly kept the quoted call inert but left the enclosing write discarded. Restoring the outer call is squarely within that mission.

Size: core paths (packages/core/src/core/**). Production 450 lines (359+/91−, one file) vs. test 638 lines (606+/32−), no generated or schema files. Under both the 500-line maintainer-awareness threshold and the 1000-line advisory. The type is fix, not refactor, so no Tier 1 hard block — and as a maintainer-authored PR it is exempt regardless.

Approach: this is where my reservation sits, and it is not about line count. The new commit changes posture from "decline the ambiguous suffix" to "decline the whole turn" — there are now ten distinct return [] sites inside recoverableToolCallBlocks. That genuinely answers the question qqqys put in the last review ("which view is authoritative when the two disagree"), but it answers it with a third option nobody has ruled on: neither view wins, the batch is dropped. It also narrows 13 previously-pinned recovery expectations, and the description states plainly that "some legitimate calls formerly recovered will now remain text." For a fallback whose entire job is rescuing malformed model output, one malformed fragment now costs the whole turn. That can be the right call — but it is a product decision, and this PR was explicitly parked on 2026-10-11 awaiting exactly that decision before this commit pre-empted it.

Worth naming too: this is round 8, past the ~5-round point where AGENTS.md says land Critical fixes only and defer the rest. The class-level rework is tracked as #13492 and the scan-cost half as #13787, and qqqys already agreed neither should hold this PR.

Risk: the deterministic Stage 1e check finds no high-risk path match. Stated honestly rather than leaning on that: xml-tool-call-fallback.ts is not on the pattern list, but it is a parser over untrusted model text whose output feeds tool dispatch — adjacent in spirit to streamingToolCallParser — so I reviewed it at the deeper depth anyway.

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪïžæœŽæŽĄæ˜Ŋ针åŊđæ–° head 5e5e7321a707 įš„ re-runčŋ™äđŸæ˜Ŋ 2026-10-11 į§ŧäšĪįŧĪ者äđ‹åŽįš„įŽŽäļ€äļŠæäšĪ。

æĻĄæŋåیæ•ī ✓äđäļŠåŋ…需标éĒ˜é―å…Ļ

é—ŪéĒ˜ïžš å·ē观æĩ‹åˆ°įš„ bugäļæ˜ŊᐆčŪšæ€§åŠ å›šã€‚#13492 äŧåĪ„äšŽ openïžŒį”ąä― æœŽäššæäšĪreview/self-reportedåĪįŽ°čŊæŪ非åļļå……åˆ†â€”â€”åŒ…æ‹Žä― åœĻäļŠäļ€äļŠ head 32acef09969d äšēč‡Šåšįš„įŦŊ到įŦŊ驌čŊïžŒįŧ“čŪšæ˜Ŋ FAILïžšäļ‰į§ CLI å―Ēæ€äļ­æœ‰äļĪį§æīū发䚆合åđķ埚įšŋäžšæ‹’įŧįš„æ–‡æĄĢåŒ–č°ƒį”Ļ。čŋ™æ˜Ŋæœ‰įŽŽä–đåĪįŽ°įš„įœŸåŪžįžšé™·ã€‚

重åĪæĢ€æŸĨ1-pre base main åģéŧ˜čŪĪ分æ”Ŋå”Ŋäļ€å…ģč”įš„ #13492 äļš OPEN因æ­Īäļč§Ķ发"å·ēčĒŦ合åđķäŋŪåĪč͆ᛖ"或"not planned"įš„įŧˆæ­Ē凚åĢ。

æ–đ向 åŊđé―ã€‚čŊĨ fallback æšč‡Š #8003é•ŋäļŠä–‡äļ‹æĻĄåž‹čū“凚čĢļ XML å·Ĩå…·č°ƒį”Ļ而非įŧ“构化数įŧ„#13515 æ­ĢįĄŪ地čŪĐčĒŦåž•į”Ļįš„č°ƒį”ĻäŋæŒæƒ°æ€§ïžŒä―†æŠŠåĪ–åą‚ write äļ€čĩ·äļĒæŽ‰äš†ã€‚æĒåĪåĪ–åą‚č°ƒį”Ļåیå…ĻåœĻčŋ™äļŠä―ŋå‘―čŒƒå›ī内。

规æĻĄïžš č§Ķ及æ ļåŋƒč·Ŋåū„packages/core/src/core/**ïž‰ã€‚į”Ÿäš§äŧĢ᠁ 450 čĄŒïžˆ359+/91−单文äŧķæĩ‹čŊ• 638 čĄŒïžˆ606+/32âˆ’ïž‰ïžŒæ— į”Ÿæˆ/schema 文äŧķã€‚ä―ŽäšŽ 500 行įŧĪ者å…ģæģĻ阈倞äđŸä―ŽäšŽ 1000 行åΧ PR åŧščŪŪ阈倞。įąŧ型æ˜Ŋ fix 而非 refactoräļč§Ķ发 Tier 1 įĄŽé˜ŧ断——äļ”ä―œäļšįŧĪč€…æœŽäššæäšĪįš„ PR æœŽå°ąčąå…ã€‚

æ–đæĄˆïžš æˆ‘įš„éĄū虑åœĻčŋ™é‡ŒïžŒäļ”äļŽčĄŒæ•°æ— å…ģ。新提äšĪ把åĪ„į―Ūæ–đ垏äŧŽ"拒įŧæœ‰æ­§äđ‰įš„åŽįž€"æ”đ成䚆"拒įŧæ•īč―ŪåŊđčŊ"——recoverableToolCallBlocks å†…įŽ°åœĻ有 十åĪ„ į‹ŽįŦ‹įš„ return []。čŋ™įĄŪåŪžå›žį­”äš† qqqys äļŠäļ€č―Ūæå‡šįš„é—ŪéĒ˜ïžˆ"äļĪäļŠč§†å›ūå†ēįŠæ—ķäŧĨ哊äļŠäļšå‡†"ïž‰ïžŒä―†įŧ™å‡šįš„æ˜ŊæēĄäššč́åۚčŋ‡įš„įŽŽäļ‰äļŠį­”æĄˆïžšäļĪäļŠéƒ―äļįŪ—ïžŒæ•đäļĒ垃。同æ—ķ有意æ”ķįŠ„äš† 13 æĄåŽŸæœŽå·ē囚åŪšįš„æĒåĪéĒ„æœŸïžŒPR 描čŋ°äđŸæ˜ŽįĄŪ写䚆"éƒĻåˆ†åŽŸæœŽčƒ―æĒåĪįš„åˆæģ•č°ƒį”ĻįŽ°åœĻ侚äŋį•™äļšæ–‡æœŽ"。åŊđ䚎äļ€äļŠčŒčīĢå°ąæ˜ŊæŒ―æ•‘į•ļå―ĒæĻĄåž‹čū“å‡šįš„ fallbackïžŒįŽ°åœĻäļ€äļŠį•ļå―ĒቇæŪĩ侚čŪĐæ•īč―ŪåŊđčŊéƒ―åĪąåŽŧæĒåĪčƒ―åŠ›ã€‚čŋ™åŊčƒ―æ˜ŊåŊđįš„å–čˆâ€”â€”ä―†čŋ™æ˜Ŋ䚧品å†ģį­–ïžŒč€ŒæœŽ PR åœĻ 2026-10-11 明įĄŪ挂čĩ·į­‰åū…įš„æ­Ģæ˜Ŋčŋ™äļŠå†ģį­–ïžŒéšåŽčŋ™äļŠæäšĪå…ˆčĄŒįŧ™å‡šäš†į­”æĄˆã€‚

åĶåĪ–éœ€č́į‚đ明čŋ™å·ēįŧæ˜ŊįŽŽ 8 č―Ūčķ…čŋ‡äš† AGENTS.md 所čŊīįšĶ 5 č―Ūäđ‹åŽåŠč― Critical äŋŪåĪã€å…ķä―™č―ŽåŽįŧ­ issue įš„į•Œé™ã€‚įąŧåˆŦįš§é‡æž„į”ą #13492 跟čŋ›ïžŒæ‰Ŧæåž€é”€į”ą #13787 跟čŋ›ïžŒqqqys äđŸå·ēčŪĪ同čŋ™äļĪč€…éƒ―äļåš”åĄä―æœŽ PR。

éĢŽé™Đïžš Stage 1e įš„įĄŪåŪšæ€§æĢ€æŸĨæēĄæœ‰å‘―äļ­éŦ˜éĢŽé™Đč·Ŋåū„ã€‚ä―†åĶ‚åŪžčŊŽč€Œäļæ˜ŊæŪæ­Īæ”ūæūïžšxml-tool-call-fallback.ts äļåœĻæĻĄåžåˆ—čĄĻ里åŊåۃæ˜Ŋč§Ģ析äļåŊäŋĄæĻĄåž‹æ–‡æœŽã€čū“凚į›īæŽĨéĐąåŠĻå·Ĩ具æīūå‘įš„č§Ģ析å™Ļ——åœĻ性čīĻäŽĨčŋ‘ streamingToolCallParser——所äŧĨ我äŧæŒ‰æ›īæ·ąįš„æ·ąåšĶ做䚆åŪĄæŸĨ。

čŋ›å…ĨäŧĢ᠁åŪĄæŸĨ 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 5e5e7321a7072d903f2d82df4e28f4aa1f80d3d1 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

What evidence this comment carries: the PR's own CI results at the reviewed commit, fetched through the API (real check names and conclusions below). This is an unattended run (GITHUB_EVENT_NAME=issue_comment), so no PR-derived code was built, run, or checked out — everything here is static reading plus GitHub's own check metadata.

Code review

My independent proposal before reading the diff: mask the quoted regions, and when ownership of a region cannot be decided, decline rather than guess. On posture the PR now matches that — arguably exceeds it, since it declines the whole batch rather than the affected block. Where I would have differed is the blast radius of the decline, which is the Stage 1 reservation rather than a code defect.

The consolidated blocker from the last review is genuinely closed, and pinned — not guarded around. qqqys named two mechanisms at 32acef09969d. Both are addressed structurally:

  • fenceGuardRanges.push(...) inside the eligibility loop is removed, so passesFenceGuard now reads a frozen pre-extension snapshot while isEligibleBlock reads the extended parameterRanges. The two views are actually compared for the first time; previously the comparator was the already-extended set and disagreement was unobservable.
  • The pass-2 branch still replaces exampleRanges rather than merging pass 1's, but the new stability check compares refreshedBlocks against eligibleBlocks by length and object identity, and declines the batch if eligibility moved at all. A documented call that pass 1 placed inside a wrapper can therefore no longer become newly executable in pass 2.

The rm -rf /tmp/x fence-axis witness qqqys measured as a regression is now committed as case R2-5 expecting ['read_file'] — matching the merge base, which is exactly what was asked for. And the test at :1502 that qqqys said encoded the opposite answer to the fence axis has been re-specified as declines a suffix that documented values move out of a prose fence, expecting []. Test and axis now agree. That resolves the "one decision needed" framing in code, if not yet by ruling.

One real defect, verified at this commit and not covered by any test. The pre-scan opening recoverableToolCallBlocks (line 335) is the only case-insensitive regex in the file:

for (const opening of text.matchAll(/<parameter(?=[\s=>])/gi)) {
  PARAMETER_OPEN_TAG_PATTERN.lastIndex = opening.index;
  if (!PARAMETER_OPEN_TAG_PATTERN.test(text)) return [];
}

It validates against PARAMETER_OPEN_TAG_PATTERN (line 15), which is case-sensitive and requires name= or =:

/<parameter(?:\s+name=(["'])[^"']+\1|=[^\s<>"']+)>/y

So <Parameter name="x">, a bare <parameter>, or an unquoted <parameter name=a> anywhere in the raw turn returns [] for the whole turn. The scan is unmasked, so this fires even when the text sits inside a code fence or inside a value being written to a file. Every other pattern in the module — TOOL_CALL_PATTERN, PARAMETER_PATTERN, PARAMETER_TAG_PATTERN — is case-sensitive, so the real parser never treats those shapes as parameter tags at all. That makes this a false positive, not a conservative call: the turn is declined where the parser saw no ambiguity to resolve.

It fails closed, so it is not a safety hole — it is an availability regression in the recovery path. I grepped the 1617-line test file: zero case-variant and zero bare <parameter> literals, so nothing pins either shape. I also grepped the repo's own prompts and docs and found no triggering text, which lowers the likelihood (the trigger is model-generated documentation, not something the taught dialect emits today) but does not remove the inconsistency. One-character fix — drop the i, or add it to the validator — plus a pin for whichever behaviour you intend.

Non-blocking, noted for completeness: positionInsideFence now computes closes before the startsInsideParameter continue, so it is dead work on parameter lines (cosmetic); and positionInsideFence is O(n·m) per call via text.slice(0, index).split('\n'), now invoked per quoted value inside maskValuesBefore plus twice per block — that is #13787's territory, and the PR explicitly disclaims a complexity claim.

Downstream consumers (named, per the core Tier 2 bar): exactly one — packages/core/src/core/llm-chat.ts:6685. It branches on if (recovery.recovered), so a declined batch is a no-op that leaves the raw XML visible in the turn, identical to base when recovery does not fire. No crash and no data loss beyond the calls not dispatching. extractXmlToolCalls has no production consumer.

Where the two mask views diverge (the flow a ruling has to cover)
sequenceDiagram
    participant P1 as recoverableToolCallBlocks
    participant P2 as Opener PreScan line 335
    participant P3 as Pass 1 example ranges
    participant P4 as Block loop
    participant P5 as Eligibility two views
    participant P6 as Pass 2 recompute
    P1->>P2: scan every parameter opener in raw text
    P2-->>P1: decline whole turn on any mismatch
    P1->>P3: derive documentation ranges over the unextended mask
    P1->>P4: match call blocks, advancing past quoted values
    P4-->>P1: decline whole turn on borrowed or unmatched regions
    P1->>P5: isEligibleBlock reads the extended mask, passesFenceGuard reads the frozen snapshot
    P5-->>P1: decline whole turn on disagreement
    P1->>P6: recompute ranges over the extended mask
    P6-->>P1: decline whole turn if eligibility changed
Loading

CI test evidence

Fetched once for the reviewed commit — 31 check-runs: 22 success, 8 skipped, 1 queued, no failures. The queued one is review-pr, a bot orchestration job rather than PR CI. Counting only pull_request-event workflow runs (not check-runs, which include this triage run itself): 2 completed, 0 pending.

The checks that matter for this diff are green: Test (ubuntu-latest, Node 22.x), Lint & Static (ubuntu-latest, Node 22.x), Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke (ubuntu-latest, Node 22.x), Desktop Shell (ubuntu-22.04) and Desktop Shell (windows-2022).

Stated plainly rather than implied: Test (macos-latest, Node 22.x), Test (windows-latest, Node 22.x) and Integration Tests (CLI, No Sandbox) are skipped, so the unit suite ran on ubuntu only. This change is platform-independent string processing, so I do not consider that a blocker — but the coverage is narrower than a full matrix, and the author's own "Tested on" table also lists Windows and Linux as not run locally.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Lint & Static (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
Test (macos-latest, Node 22.x) skipped
Test (windows-latest, Node 22.x) skipped
Integration Tests (CLI, No Sandbox) skipped
review-pr queued (bot orchestration, not PR CI)

Sandboxed verification would settle this: @qwen-code /verify. The central claim is behavioural — documented calls stay inert while the outer call is recovered — and a green suite cannot substantiate it here, for a reason this PR's own history demonstrates: at 32acef09969d the narrow suite was 115/115 green in both arms while the real CLI dispatched a documented rm -rf /tmp/x that the merge base refused. Green did not mean pinned.

The specific gap at this head: the new CLI evidence covers two shapes (the extra-quote case and one outer-write control), but the FAIL that prompted this commit was measured across three CLI shapes — and cells 2 and 3 (a prose <example> closed one tag too early; an example region dropped by the extended-mask recompute) were the actual regressions. Those two are now covered by module-level corpus cases and by the new R2-5/R4-2 unit pins, which is real progress, but they have not been re-driven end-to-end at 5e5e7321a707 against the base build. A /verify A/B on those two shapes plus the R2-5 fence axis would close it. You have write access, so /tmux is also available for the TUI surface; a maintainer can trigger either.

Real-scenario testing

N/A — unattended CI run. Per the skill's rules no PR-derived code was executed, built, or checked out, so there is no terminal capture to inline. The live-behaviour gap is named above with the lane that would close it, rather than papered over with the author's self-reported tmux results — those are attributed in the PR description as the author's claim, and are not evidence this gate re-ran.

äļ­æ–‡čŊŽ

æœŽæĄčŊ„čŪšæšåļĶįš„čŊæŪïžš 通čŋ‡ API 取åū—įš„ã€čŊĨ PR 臩čšŦåœĻ所åŪĄ commit äļŠįš„ CI įŧ“果ä–đäļšįœŸåŪžæĢ€æŸĨ名äļŽįŧ“čŪšïž‰ã€‚æœŽæŽĄäļšæ— äššå€žåۈčŋčĄŒïžˆGITHUB_EVENT_NAME=issue_comment因æ­ĪæēĄæœ‰æž„åŧšã€čŋčĄŒæˆ– checkout äŧŧä―•æĨ臩 PR įš„äŧĢį â€”â€”å…ĻéƒĻ内åŪđæĨč‡Šé™æ€é˜…čŊŧ加 GitHub 臩čšŦįš„æĢ€æŸĨ元数æŪ。

äŧĢ᠁åŪĄæŸĨïžš äļŠäļ€č―Ū合åđķåŽįš„é˜ŧ断é—Ūéǘčŋ™æŽĄæ˜Ŋᜟæ­ĢčĒŦįŧ“构性å…ģé—­įš„ïžŒåđķäļ”čĒŦæĩ‹čŊ•囚åŪšä―äš†ïžŒäļæ˜Ŋ再įŧ•äļ€åą‚åۈåŦ。qqqys åœĻ 32acef09969d æŒ‡å‡šįš„äļĪäļŠæœšåˆķéƒ―å·ēåĪ„į†ïžšå…ķäļ€ïžŒeligibility åūŠįŽŊå†…įš„ fenceGuardRanges.push(...) å·ēčĒŦ删é™Ī䚎æ˜Ŋ passesFenceGuard čŊŧ取æ‰Đåą•å‰įš„å†ŧįŧ“åŋŦį…§ïžŒč€Œ isEligibleBlock čŊŧ取æ‰Đåą•åŽįš„ parameterRanges——äļĪäļŠč§†å›ūįŽŽäļ€æŽĄįœŸæ­ĢčĒŦæ‹ŋæĨæŊ”čūƒïžˆæ­Ī前æŊ”čūƒå™ĻčŊŧåˆ°įš„å·ēæ˜Ŋæ‰Đåą•åŽįš„é›†åˆïžŒå†ēįŠæ đ朎无æģ•č§‚æĩ‹ïž‰ïž›å…ķäšŒïžŒįŽŽäšŒéč™―į„ķäŧæ˜Ŋæ›ŋæĒč€Œéžåˆåđķ exampleRangesïžŒä―†æ–°åĒžįš„įĻģåŪšæ€§æĢ€æŸĨ䞚按é•ŋåšĶäļŽåŊđčąĄåŒä€§æŊ”čūƒ refreshedBlocks 和 eligibleBlocks及č́čĩ„æ žé›†åˆå‘į”Ÿäŧŧä―•å˜åŒ–å°ąæ•đ拒įŧïžŒå› æ­ĪįŽŽäļ€éčĒŦåˆĪåۚåĪ„äšŽæ–‡æĄĢ包čĢđå†…įš„č°ƒį”ĻäļåŊčƒ―åœĻįŽŽäšŒéå˜æˆåŊæ‰§čĄŒã€‚

qqqys åŪžæĩ‹äļšå›žå―’įš„ rm -rf /tmp/x fence å―Ēæ€ïžŒįްå·ēä―œäļš R2-5 į”Ļäū‹æäšĪ期望倞 ['read_file']äļŽåˆåđķ埚įšŋäļ€č‡īæ­Ģæ˜Ŋå―“æ—ķčĶæą‚įš„ã€‚qqqys 指凚äļŽ fence åˆĪåۚ"įž–į äš†į›ļåį­”æĄˆ"įš„ :1502 æĩ‹čŊ•äđŸå·ē重写äļš declines a suffix that documented values move out of a prose fence期望 []。æĩ‹čŊ•äļŽåˆĪåۚč―īįŽ°å·ēäļ€č‡ī。

äļ€äļŠįœŸåŪžįžšé™·ïžŒå·ēåœĻčŊĨ commit æ ļåŪžïžŒä— äŧŧä―•æĩ‹čŊ•čĶ†į›–ïžš įŽŽ 335 čĄŒįš„éĒ„æ‰Ŧ描æ˜Ŋæ•īäļŠæ–‡äŧķäļ­å”Ŋäļ€åļĶ i 标åŋ—įš„æ­Ģ则/<parameter(?=[\s=>])/giåīį”ĻåĪ§å°å†™æ•æ„Ÿã€äļ”čĶæą‚ name= 或 = įš„ PARAMETER_OPEN_TAG_PATTERNïžˆįŽŽ 15 čĄŒïž‰åŽŧæ ĄéŠŒã€‚å› æ­Ī原始文朎äļ­äŧŧä―•ä―į―Ūå‡šįŽ° <Parameter name="x">、čĢļ <parameter> æˆ–æœŠåŠ åž•å·įš„ <parameter name=a>ïžŒéƒ―äžščŪĐæ•īč―ŪåŊđčŊčŋ”回 []。čŊĨæ‰Ŧ描朊įŧäŧŧä―• mask所äŧĨåģäūŋčŋ™æŪĩæ–‡æœŽä―äšŽäŧĢ᠁å›īæ å†…ã€æˆ–ä―äšŽæ­ĢåœĻ写å…Ĩ文äŧķįš„å€žé‡ŒïžŒäđŸåŒæ ·č§Ķ发。æĻĄå—内å…ķä―™æ‰€æœ‰æ­Ģ则TOOL_CALL_PATTERN、PARAMETER_PATTERN、PARAMETER_TAG_PATTERNïž‰éƒ―æ˜ŊåĪ§å°å†™æ•æ„Ÿįš„ïžŒįœŸæ­Ģįš„č§Ģ析å™Ļæ đ朎äļäžšæŠŠčŋ™äš›å―Ēæ€å―“ä―œ parameter 标į­ū。所äŧĨčŋ™æ˜ŊčŊŊåˆĪïžŒč€ŒéžäŋåۈåˆĪæ–­ïžšč§Ģ析å™Ļįœžäļ­æŊŦ无歧äđ‰įš„äļ€č―ŪåŊđčŊčĒŦ拒įŧäš†ã€‚

åۃæ˜Ŋ fail-closed įš„ïžŒå› æ­Īäļæ˜ŊåŪ‰å…Ļ枏æīžïžŒč€Œæ˜ŊæĒåĪč·Ŋåū„äļŠįš„åŊį”Ļæ€§å›žå―’。我åœĻ 1617 行æĩ‹čŊ•æ–‡äŧķäﭿ̀įīĒïžšåĪ§å°å†™å˜ä―“äļŽčĢļ <parameter> 字éĒ量均äļšé›ķäļĪį§å―Ēæ€éƒ―æēĄæœ‰čĒŦ囚åŪšã€‚æˆ‘äđŸæĢ€įīĒ乆äŧ“åš“č‡ŠčšŦįš„ prompt äļŽæ–‡æĄĢïžŒæœŠå‘įŽ°åŊč§Ķ发文朎——čŋ™é™ä―Žäš†å‘į”ŸæĶ‚įŽ‡ïžˆč§Ķ发暐æ˜ŊæĻĄåž‹į”Ÿæˆįš„æ–‡æĄĢæ–‡æœŽïžŒč€Œéžå―“å‰æ‰€æ•™æ–đčĻ€äžščū“å‡šįš„å†…åŪđïž‰ïžŒä―†åđķäļæķˆé™Īčŋ™åĪ„äļäļ€č‡ī。äŋŪåĪåŠéœ€äļ€äļŠå­—įŽĶïžšåŽŧ掉 i或įŧ™æ ĄéЌæ­Ģ则加äļŠ i——åđķäļšä― æƒģčĶįš„čĄŒäļščĄĨäĄæĩ‹čŊ•。

非é˜ŧ断äŧ…ä―œčŪ°å―•ïžšpositionInsideFence įŽ°åœĻåœĻ startsInsideParameter įš„ continue äđ‹å‰čŪĄįŪ— closesåŊđ parameter čĄŒåąžäšŽæ— į”ĻčŪĄįŪ—ïžˆįšŊč§‚æ„Ÿïž‰ïž›åĶåĪ– positionInsideFence æŊæŽĄč°ƒį”Ļæ˜Ŋ O(n·m)text.slice(0, index).split('\n')ïž‰ïžŒįŽ°åœĻåœĻ maskValuesBefore äļ­åŊđæŊäļŠåž•į”Ļå€žč°ƒį”Ļäļ€æŽĄã€åŊđæŊäļŠ block 调į”ĻäļĪæŽĄâ€”â€”čŋ™åąžäšŽ #13787 įš„čŒƒå›īPR äđŸæ˜ŽįĄŪäļåŪĢį§°åĪæ‚åšĶæ”đ善。

äļļæķˆčīđæ–đ按æ ļåŋƒ Tier 2 čĶæą‚é€äļ€į‚đ名 及有äļ€äļŠâ€”—packages/core/src/core/llm-chat.ts:6685。åۃäŧĨ if (recovery.recovered) 分æ”Ŋ因æ­Īæ•đčĒŦ拒æ—ķæ˜Ŋæ— æ“ä―œïžŒčĢļ XML åŽŸæ ·į•™åœĻčŊĨč―Ūæ–‡æœŽäļ­ïžŒäļŽåŸšįšŋ朊č§Ķ发æĒåĪæ—ķåیå…Ļäļ€č‡ī。äļäžšåīĐæšƒïžŒé™Īå·Ĩå…·č°ƒį”Ļ朊æīū发åĪ–æ— æ•°æŪäļĒåĪąã€‚extractXmlToolCalls æ— į”Ÿäš§æķˆčīđæ–đ。

CI čŊæŪïžš åœĻ所åŪĄ commit äļŠäļ€æŽĄæ€§å–åū— 31 äļŠ check-run——22 成功、8 č·ģčŋ‡ã€1 排队无åĪąčīĨã€‚æŽ’é˜Ÿįš„æ˜Ŋ review-prïžŒåąžäšŽæœšå™Ļäššįž–æŽ’äŧŧåŠĄč€Œéž PR CI。及įŧŸčŪĄ pull_request 之äŧķįš„ workflow runäļåŦ check-run因äļšåŽč€…包åŦæœŽæŽĄ triage 臩čšŦ2 äļŠå·ēåŪŒæˆã€0 äļŠåū…čŋčĄŒã€‚äļŽæœŽ diff į›ļå…ģįš„æĢ€æŸĨ均äļšįŧŋč‰ē。åĶ‚åŪžčŊŽïžšTest (macos-latest)、Test (windows-latest) äļŽ Integration Tests (CLI, No Sandbox) äļšč·ģčŋ‡ïžŒå› æ­Ī单æĩ‹äŧ…åœĻ ubuntu äļŠčŋčĄŒčŋ‡ã€‚朎æ”đåŠĻæ˜Ŋåđģ台无å…ģįš„å­—įŽĶäļēåĪ„į†ïžŒæˆ‘äļčŪĪäļščŋ™æž„成é˜ŧæ–­â€”â€”ä―†čĶ†į›–čŒƒå›īįŠ„äšŽåیæ•īįŸĐé˜ĩïžŒä―œč€…č‡Šå·ąįš„ "Tested on" čĄĻæ žäđŸæŠŠ Windows äļŽ Linux 标äļšæœŠåœĻ朎地čŋčĄŒã€‚

æē™įŪąéŠŒčŊåŊäŧĨåۚčŪšïžš@qwen-code /verify。 æ ļåŋƒäļŧåž æ˜Ŋ行äļšæ€§įš„——文æĄĢåŒ–č°ƒį”ĻäŋæŒæƒ°æ€§ã€åĪ–åą‚č°ƒį”ĻčĒŦæĒåĪâ€”â€”č€Œįŧŋč‰ēæĩ‹čŊ•åœĻæ­Ī无æģ•čŊåŪžåŪƒïžŒį†į”ąæ­Ģæ˜Ŋ朎 PR č‡Šå·ąįš„åŽ†åēïžšåœĻ 32acef09969d äļŠįŠ„æĩ‹čŊ•集äļĪäū§éƒ―æ˜Ŋ 115/115 å…ĻįŧŋïžŒč€ŒįœŸåŪž CLI åīæīū发䚆合åđķ埚įšŋäžšæ‹’įŧįš„æ–‡æĄĢ化 rm -rf /tmp/x。įŧŋäļį­‰äšŽčĒŦ囚åŪšä―ã€‚

čŊĨ head äļŠįš„å…·ä―“įžšåĢïžšæ–°įš„ CLI čŊæŪ及č͆ᛖäļĪį§å―Ēæ€ïžˆéĒåĪ–åž•å·į”Ļäū‹äļŽäļ€äļŠåĪ–åą‚ write åŊđį…§ïž‰ïžŒä―†äŋƒä―ŋæœŽæŽĄæäšĪįš„ FAIL æ˜ŊåœĻäļ‰į§ CLI å―Ēæ€äĩ‹åū—įš„â€”â€”å…ķäļ­ cell 2 和 cell 3prose <example> 提前äļ€äļŠæ ‡į­ū闭合example 匚域čĒŦæ‰Đåą• mask 重įŪ—äļĒ垃才æ˜Ŋᜟæ­Ģįš„å›žå―’ã€‚čŋ™äļĪč€…įŽ°åœĻ有æĻĄå—įš§čŊ­æ–™į”Ļäū‹å’Œæ–°åĒžįš„ R2-5/R4-2 单æĩ‹čĶ†į›–ïžŒæ˜ŊåŪžčīĻčŋ›åą•ïžŒä―†å°šæœŠåœĻ 5e5e7321a707 äļŠį›ļåŊđ埚įšŋ构åŧšé‡æ–°åščŋ‡įŦŊ到įŦŊ驌čŊã€‚åŊđčŋ™äļĪį§å―Ēæ€åŠ  R2-5 fence č―ī做äļ€æŽĄ /verify A/B åģåŊ闭įŽŊã€‚ä― æœ‰å†™æƒé™ïžŒå› æ­Ī /tmux äđŸåŊį”Ļ䚎 TUI åą‚éĒïž›äļĪč€…éƒ―åŊį”ąįŧĪ者č§Ķ发。

ᜟåŪžåœšæ™Ŋæĩ‹čŊ•ïžš N/A —— 无䚚倞åۈ CI čŋčĄŒã€‚按 skill č§„åˆ™æœŠæ‰§čĄŒã€æž„åŧšæˆ– checkout äŧŧä―•æĨ臩 PR įš„äŧĢį ïžŒå› æ­ĪæēĄæœ‰åŊå†…č”įš„įŧˆįŦŊæ•čŽ·ã€‚äļŠčŋ°čĄŒäļšįžšåĢæ˜ŊčŋžåŒåŊäŧĨ闭įŽŊįš„æ‰§čĄŒé€šé“äļ€čĩ·į‚đæ˜Žįš„ïžŒč€Œäļæ˜Ŋį”Ļä―œč€…č‡ŠæŠĨįš„ tmux įŧ“æžœæĨåĄŦčĄĨ——é‚Ģ䚛内åŪđåœĻ PR 描čŋ°äļ­å·ē明įĄŪå―’åąžäļšä―œč€…äļŧ垠åđķ非朎é—ļé—ĻåĪč·‘čŋ‡įš„čŊæŪ。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 5e5e7321a7072d903f2d82df4e28f4aa1f80d3d1 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — the consolidated blocker is genuinely closed and pinned for the first time, but the fix answers a question a maintainer was asked to rule on, and I found one untested defect on the way.

Stepping back: this is the first round that answered qqqys's "which view is authoritative" with a structural change instead of another entrance guard. Removing the in-loop fenceGuardRanges.push(...) makes the two views actually comparable, and the pass-2 stability check means a documented call can no longer be promoted to executable by the mask extension. The rm -rf /tmp/x fence witness is committed as R2-5 expecting the merge-base answer. That is real convergence, and it is the reason I am not requesting changes.

What keeps me from approving:

The posture change is a product decision that has not been made. "Decline the whole turn" is now reached from ten separate sites, and it costs legitimate recovery in 13 shapes that used to be pinned — the description says so plainly. For a fallback whose whole purpose is rescuing malformed model output, one bad fragment now forfeits the entire turn, and llm-chat.ts:6685 turns that into raw XML left visible to the user. That may well be the right trade (it is the safe direction, and the alternative is dispatching documentation), but it is exactly the scoping question this PR was parked on 2026-10-11 to get answered, and the commit pre-empted the answer rather than waiting for it. A gate should not ratify that on the author's behalf.

One verified defect stands between this and clean. The line-335 pre-scan is the only case-insensitive regex in the file, validated against a case-sensitive pattern that also requires name= or =. <Parameter name="x">, a bare <parameter>, or <parameter name=a> anywhere in the raw turn — including inside a fence or inside a value being written out — declines everything. The real parser is case-sensitive throughout, so these shapes are not ambiguous to it at all; this is a false positive, not conservatism. It fails closed, so it is not a safety hole, and nothing in the repo's own prompts or docs triggers it today, which is why I am calling it a defect to fix rather than a reason to reject. No test pins either shape.

The behavioural claim is not yet substantiated end-to-end at this head. The prior head's narrow suite was 115/115 green in both arms while the real CLI dispatched a documented rm -rf. The new head's CLI evidence covers two shapes; the two cells that actually regressed are now pinned at unit level but have not been re-driven against the base build. @qwen-code /verify on those two plus the R2-5 axis would close it, and Stage 2 names that gap specifically.

Also worth saying out loud, since it shapes how much more this PR should absorb: this is round 8. AGENTS.md asks that past roughly five rounds a PR land Critical fixes only and defer the rest, and the two things still open beyond the defect above — the recovery-surface class (R2-1) and the unbounded scan cost — are already tracked as #13492 and #13787, with qqqys on record that neither should hold this PR. I agree. If the ruling is "keep it open", the remaining work is the one-character flag fix, its pin, and a /verify run — not another round of guards.

Verdict: defer, not approve, not reject. I am escalating rather than deciding, because the blocking uncertainty is not resolvable from the diff, the tests, or the description — it needs someone to own the compatibility trade-off. The PR already carries CHANGES_REQUESTED and stays gated either way, so deferring unblocks nothing.

@qqqys — you asked the decisive question in the last review, and this commit answers it with a third option (neither view wins; the batch is dropped) plus a re-specified pin at :1502. Two things need your ruling: whether whole-turn decline is the accepted answer to "which view is authoritative", and whether that is acceptable given the 13 narrowed recovery expectations. The line-335 case-insensitivity defect is independent of that ruling and should be fixed regardless. @DennisYu07 is already assigned.

äļ­æ–‡čŊŽ

į―ŪäŋĄåšĶïžš3/5 —— 合åđķåŽįš„é˜ŧ断é—ŪéĒ˜įŽŽäļ€æŽĄčĒŦᜟæ­Ģå…ģ闭åđķ囚åŪšä―ïžŒä―†čŋ™æŽĄäŋŪåĪå›žį­”įš„æ˜Ŋäļ€äļŠæœŽåš”į”ąįŧĪ者č́åŪšįš„é—ŪéĒ˜ïžŒč€Œäļ”čŋ‡įĻ‹äļ­æˆ‘å‘įŽ°äš†äļ€äļŠæœŠčĒŦæĩ‹čŊ•čĶ†į›–įš„įžšé™·ã€‚

退ä­Ĩįœ‹ïžščŋ™æ˜ŊįŽŽäļ€č―Ūį”Ļįŧ“构性æ”đåŠĻã€č€Œäļæ˜Ŋ再加äļ€åą‚å…ĨåĢåۈåŦæĨå›žį­” qqqys "äļĪäļŠč§†å›ūå†ēįŠæ—ķäŧĨ哊äļŠäļšå‡†"įš„æé—Ū。删é™ĪåūŠįŽŊå†…įš„ fenceGuardRanges.push(...) čŪĐäļĪäļŠč§†å›ūįŽŽäļ€æŽĄįœŸæ­ĢåŊæŊ”ïž›įŽŽäšŒéįš„įĻģåŪšæ€§æĢ€æŸĨ意å‘ģį€æ–‡æĄĢåŒ–č°ƒį”ĻäļåŊčƒ―å†å›  mask æ‰Đåą•č€ŒčĒŦ提升äļšåŊæ‰§čĄŒã€‚rm -rf /tmp/x įš„ fence čŊäū‹å·ēä―œäļš R2-5 提äšĪ期望倞äļŽåˆåđķ埚įšŋäļ€č‡ī。čŋ™æ˜ŊåŪžčīĻæ€§įš„æ”ķ敛äđŸæ˜Ŋ我æēĄæœ‰é€‰æ‹Đ request changes įš„åŽŸå› ã€‚

čŪĐæˆ‘äļæ‰đå‡†įš„į†į”ąïžš

åĪ„į―Ūæ–đåžįš„æ”đ变æ˜Ŋäļ€äļŠå°šæœŠåšå‡šįš„䚧品å†ģį­–ã€‚ įŽ°åœĻ"拒įŧæ•īč―ŪåŊđčŊ"åŊäŧĨäŧŽååĪ„äļåŒä―į―Ūč§Ķ发äŧĢäŧ·æ˜Ŋ 13 äļŠåŽŸæœŽå·ē囚åŪšįš„å―Ēæ€åĪąåŽŧ合æģ•æĒåĪčƒ―åŠ›â€”â€”PR 描čŋ°åŊđæ­Ī写åū—åūˆæ˜Žį™―。åŊđ䚎äļ€äļŠčŒčīĢå°ąæ˜ŊæŒ―æ•‘į•ļå―ĒæĻĄåž‹čū“å‡šįš„ fallbackäļ€äļŠåį‰‡æŪĩå°ąäžščŪĐæ•īč―ŪåŊđčŊå…ĻéƒĻä―œåšŸïžŒč€Œ llm-chat.ts:6685 䞚把åŪƒå˜æˆį”Ļ户åŊč§įš„čĢļ XML。čŋ™äļŠå–čˆåūˆåŊčƒ―æ˜ŊåŊđįš„ïžˆæ–đ向æ˜ŊåŪ‰å…Ļįš„ïžŒåĶäļ€į§é€‰æ‹Đæ˜Ŋ把文æĄĢæīū发凚åŽŧïž‰ïžŒä―†åŪƒæ°æ°å°ąæ˜Ŋ朎 PR åœĻ 2026-10-11 挂čĩ·į­‰åū…į­”æĄˆįš„é‚ĢäļŠčŒƒå›īé—ŪéĒ˜ïžŒč€Œčŋ™äļŠæäšĪå…ˆčĄŒįŧ™å‡šäš†į­”æĄˆïžŒæēĄæœ‰į­‰č́åŪšã€‚é—ļé—Ļäļåš”äŧĢæ›ŋä―œč€…æ‰đ准čŋ™äļ€į‚đ。

äļ€äļŠå·ēæ ļåŪžįš„įžšé™·æĻŠåœĻ"åđē净"äđ‹å‰ã€‚ įŽŽ 335 čĄŒįš„éĒ„æ‰Ŧ描æ˜Ŋ文äŧķäļ­å”Ŋäļ€åĪ§å°å†™äļæ•æ„Ÿįš„æ­Ģ则åīį”ĻåĪ§å°å†™æ•æ„Ÿã€äļ”čĶæą‚ name= 或 = įš„æĻĄåžåŽŧæ ĄéŠŒã€‚åŽŸå§‹æ–‡æœŽäļ­äŧŧä―•ä―į―Ūå‡šįŽ° <Parameter name="x">、čĢļ <parameter> 或 <parameter name=a>——包拎åœĻå›ī栏内、或åœĻæ­ĢåœĻå†™å‡šįš„å€žé‡Œâ€”â€”éƒ―äžšæ‹’įŧå…ĻéƒĻ内åŪđã€‚č€ŒįœŸæ­Ģįš„č§Ģ析å™Ļ通įŊ‡åĪ§å°å†™æ•æ„ŸïžŒčŋ™äš›å―Ēæ€åŊđåŪƒč€ŒčĻ€æ đ朎äļæž„成歧äđ‰ïž›æ‰€äŧĨčŋ™æ˜ŊčŊŊåˆĪäļæ˜ŊäŋåŪˆã€‚åۃæ˜Ŋ fail-closed įš„ïžŒå› æ­Īäļæ˜ŊåŪ‰å…Ļ枏æīžïž›äŧ“åš“č‡ŠčšŦįš„ prompt äļŽæ–‡æĄĢį›Ū前äđŸäļäžšč§Ķ发åŪƒïžŒčŋ™æ­Ģæ˜Ŋ我把åۃåŪšæ€§äļš"åū…äŋŪįžšé™·"而非"拒įŧį†į”ą"įš„åŽŸå› ã€‚äļĪį§å―Ēæ€éƒ―æēĄæœ‰æĩ‹čŊ•囚åŪšã€‚

行äļšäļŧåž åœĻčŊĨ head äļŠå°šæœŠįŦŊ到įŦŊčŊåŪžã€‚ äļŠäļ€äļŠ head įš„įŠ„æĩ‹čŊ•集äļĪäū§éƒ―æ˜Ŋ 115/115 å…ĻįŧŋïžŒč€ŒįœŸåŪž CLI æīū发䚆文æĄĢåŒ–įš„ rm -rf。新 head įš„ CLI čŊæŪč͆ᛖäļĪį§å―Ēæ€ïž›įœŸæ­Ģå›žå―’įš„é‚ĢäļĪäļŠ cell įŽ°åœĻ有单æĩ‹å›šåŪšïžŒä―†å°šæœŠį›ļåŊđ埚įšŋ构åŧšé‡æ–°éĐąåŠĻčŋ‡ã€‚åŊđčŋ™äļĪč€…åŠ  R2-5 č―īč·‘äļ€æŽĄ @qwen-code /verify åģåŊ闭įŽŊStage 2 å·ēå…·ä―“į‚đ名čŊĨįžšåĢ。

čŋ˜æœ‰äļ€į‚đåš”å―“æ˜ŽčŊī因äļšåۃå†ģåۚčŋ™äļŠ PR čŋ˜åš”čŊĨåļæ”ķåĪšå°‘æ”đåŠĻïžščŋ™å·ēæ˜ŊįŽŽ 8 č―Ū。AGENTS.md čĶæą‚čķ…čŋ‡åΧįšĶ 5 č―Ūäđ‹åŽåŠč― Critical äŋŪåĪã€å…ķä―™č―ŽåŽįŧ­åĪ„į†ïž›č€Œé™ĪäļŠčŋ°įžšé™·äđ‹åĪ–äŧæœŠå…ģé—­įš„äļĪäŧķ之——æĒåĪéĒįąŧåˆŦé—ŪéĒ˜ïžˆR2-1äļŽæ— į•Œæ‰Ŧ描垀销——å·ē分åˆŦį”ą #13492 和 #13787 跟čŋ›ïžŒqqqys äđŸå·ē明įĄŪčĄĻįĪščŋ™äļĪč€…éƒ―äļåš”åĄä―æœŽ PR。我同意。åĶ‚æžœč́åۚæ˜Ŋ"äŋæŒåž€åŊ"å‰Đä―™å·Ĩä―œå°ąæ˜Ŋäļ€äļŠå­—įŽĶįš„æ ‡åŋ—äŋŪåĪã€åۃåŊđåš”įš„æĩ‹čŊ•囚åŪšïžŒäŧĨ及äļ€æŽĄ /verify——而äļæ˜Ŋ再æĨäļ€č―ŪåۈåŦ。

įŧ“čŪšïžšæš‚įž“ïžŒäļæ‰đ准äđŸäļæ‹’įŧã€‚ 我选æ‹ĐäļŠå‡č€Œäļæ˜Ŋ臩行åˆĪåŪšïžŒå› äļščŋ™äļŠé˜ŧ断性äļįĄŪåŪšæ€§æ— æģ•äŧŽ diff、æĩ‹čŊ•或描čŋ°äļ­č§Ģå†ģ——需čĶæœ‰äššæĨæ‰ŋ担čŋ™äļŠå…žåŪđæ€§å–čˆã€‚čŊĨ PR å·ēåļĶæœ‰ CHANGES_REQUESTED无čŪšå“Šį§įŧ“čŪšéƒ―äŧåĪ„äšŽé˜ŧ断įŠķ态因æ­Īæš‚įž“äļäžšæ”ū垀äŧŧä―•äļœčĨŋ。

@qqqys —— å†ģåŪšæ€§įš„é—Ūéǘæ˜Ŋä― åœĻäļŠäļ€č―Ūæå‡šįš„ïžŒč€ŒæœŽæŽĄæäšĪį”ĻįŽŽäļ‰äļŠé€‰éĄđå›žį­”äš†åŪƒïžˆäļĪäļŠč§†å›ūéƒ―äļįŪ—ïžŒæ•đäļĒ垃åđķ重写䚆 :1502 įš„å›šåۚæĩ‹čŊ•。有äļĪäŧķ之需čĶä― įš„č́åŪšïžšæ•īč―Ūæ‹’įŧæ˜ŊåĶæ˜Ŋ"äŧĨ哊äļŠč§†å›ūäļšå‡†"įš„åŊæŽĨå—į­”æĄˆïž›äŧĨåŠč€ƒč™‘åˆ° 13 æĄčĒŦæ”ķįŠ„įš„æĒåĪéĒ„æœŸïžŒčŋ™äļŠå–čˆæ˜ŊåĶåŊäŧĨæŽĨå—ã€‚įŽŽ 335 čĄŒįš„åĪ§å°å†™äļæ•æ„Ÿįžšé™·äļŽčŊĨč́åŪšæ— å…ģ无čŪšåĶ‚ä―•éƒ―åš”äŋŪåĪã€‚@DennisYu07 å·ēåœĻ assignee äļ­ã€‚

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 5e5e7321a7072d903f2d82df4e28f4aa1f80d3d1 · re-run with @qwen-code /triage

@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

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: 267 passed · 1 failed · 268 total

Flakiness gate: ✅ 1 changed test file(s) x 5 identical rounds, no divergence

äļ­æ–‡ — åˆĪåŪšïžšâŒ äļé€ščŋ‡ · æŠĨå‘Šäš†å‘įŽ°ïžˆagent åˆĪåŪšïž‰

æē™įŪąéŠŒčŊåœĻ隔įĶŧ、无凭čŊįš„åŪđå™Ļäļ­æ‰§čĄŒäš†čŊĨ PR įš„äŧĢį ïžˆäļŽ base 构åŧš A/B åŊđį…§ã€æ—  mock harness 断čĻ€ã€åŪšå‘é—ĻįĶïž‰ã€‚äŧ…ä―œäļščŊ„åŪĄčŊæŪäļæž„成čŊ„åŪĄã€æ‰đ准或 CI æĢ€æŸĨ。

č„šæœŽæ–­čĻ€ïžš267 通čŋ‡ · 1 åĪąčīĨ · 268 æ€ŧčŪĄ

抖åŠĻé—Ļ✅ 1 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #13579 deep verification — fix(core): recover outer XML calls with quoted call content

Verdict: findings — 268 scripted assertions executed, 267 pass / 1 fail.
Verified head OID dc36fc7378fdc4cc25b7f17717baa3cc81a8055e (git rev-parse HEAD^2), base tip 8003d280420da32ddff4f9b0ef9a7b0aff42a8bc (HEAD^1).

The central claim is proven load-bearing by A/B and holds across a 24-shape sibling sweep: the outer write is recovered, the quoted call stays inert on every shape, and malformed/truncated inputs stay conservative. The single failing assertion is a lost regression pin on a security guard (Finding 1), not a defect in shipped behaviour.

äļ­æ–‡æ‘˜č́

įŧ“čŪšïžšfindings — å…ąæ‰§čĄŒ 268 éĄđč„šæœŽæ–­čĻ€ïžŒ267 通čŋ‡ / 1 åĪąčīĨ。銌čŊ head äļš dc36fc73base äļš 8003d280。

A/B įŧ“čŪšïžšæ ļåŋƒäļŧ垠成įŦ‹ã€‚issue #13492 įš„čū“å…ĨåœĻ base äļŠ recovered=false、æĒåĪ 0 äļŠč°ƒį”Ļïž›åœĻ head äĒåĪå‡š 1 äļŠ write_file参数倞逐字äŋį•™åیæ•īåž•į”Ļ内åŪđčĒŦåž•į”Ļįš„ run_shell_command åœĻäļĪäū§éƒ―äļæīū发。base åŊđį…§æ žæŒ‰éĒ„æœŸåĪąčīĨåģčŊĨ断čĻ€é€ščŋ‡ïž‰ã€‚24 äļŠåŒæ—å―Ēæ€å…ĻéƒĻįŽĶ合éĒ„æœŸïžŒč§äļ‹čĄĻ A/B äļŽåŒæ—æ‰Ŧ描įŸĐé˜ĩ。

findingsïžš

  1. čūƒé‡ïžŒæĩ‹čŊ•č͆ᛖ / åŪ‰å…ĻæŠĪ栏 朎 PR æ”đ写䚆å”Ŋäļ€é’‰ä― fix(core): keep an XML tool call quoted in a parameter value inert #13515「垕į”Ļ调į”ĻäŋæŒæƒ°æ€§ã€æŠĪæ įš„æĩ‹čŊ•。į§ŧé™ĪčŊĨæŠĪ栏后č§Ģ析å™Ļ 83 éĄđäļŽč°ƒį”Ļæ–đ 553 éĄđæĩ‹čŊ•å…Ļįŧŋïž›ä―†éĐąåŠĻčŊĨå˜åž‚ä―“æž„åŧšåŊč§ïžšhead äļæīūå‘ïžŒå˜åž‚ä―“æīū发凚 run_shell_commandrm -rf /tmp/x2 äĄéšæœščū“å…Ĩäļ­æœ‰ 341 æĄåŊ匚分。åŧščŪŪčĄĨįš„å›žå―’æĩ‹čŊ•å·ēåŪžæĩ‹ïžšåŠ å…Ĩ后 head äļš 84/84 å…Ļįŧŋį§ŧé™ĪæŠĪ栏æ—ķ恰åĨ―及有čŊĨ新æĩ‹čŊ•变įšĒ。
  2. čūƒč―ŧïžŒæ€§čƒ―ïž‰ 新æĒåĪįš„å―Ēæ€äžšč§ĶčūūäĄæ—Ēæœ‰įš„čķ…įšŋ性č·Ŋåū„ïžšn=1000113 KBæ—ķ base 25.4 ms → head 3733 msn=2000 æ—ķ head čķ…čŋ‡ 15 s äļŠé™ã€‚į­‰å­—čŠ‚æ•°ã€į­‰å—æ•°įš„æ— åž•į”ĻåŊđį…§åœĻäļĪäū§åŒäļšįšĶ 2.8 sčŊæ˜ŽäļŧåŊžéĄđæ˜Ŋæ—Ēæœ‰įš„ïž›PR 臩čšŦ新åĒžæ‰Ŧ描įšĶ占 914 msã€‚įœŸåŪž issue å―Ēæ€ïžˆå•äļŠč°ƒį”Ļåž•į”ĻįĪšäū‹ïž‰äŧäļšįšŋ性295 KB æ—ķ 114.9 ms。
  3. 提įĪšïžŒæčŋ°ïž‰ PR 同æ—ķ把 0.8 æ•Ģ文æŊ”äū‹ã€Œæ„å›ūæŠĪ栏」äŧŽåšĶ量原文æ”đäļšåšĶ量å·ēį§ŧé™ĪæĒåĪå—įš„æ–‡æœŽïžŒæčŋ°äļ­įš„ Risk & Scope 朊提及。åŪžæĩ‹ïžšäŧ…åŊđ 1 äļŠæ–°æĩ‹čŊ•čĩ·ä―œį”Ļïž›4 äĄéšæœščū“å…Ĩäļ­äŧŽæœŠæ–°åĒžæīū发2 æŽĄåč€ŒæŠ‘åˆķ乆æīū发æ–đ向æ›īåŪ‰å…Ļ。
  4. åđæŊ›æą‚į–ĩ 匚é—ī合åđķå°ąåœ°äŋŪæ”đ乆äļŽ quotedValueRanges å…ąäšŦįš„æ•°įŧ„åˆŦ名。4 äĄéšæœščū“å…Ĩäļ‹čĄŒäļšåیå…Ļäļ€č‡ī0 åĪ„å·Ūåž‚ïž‰ïžŒå―“å‰äļščŊ­äđ‰æ— æ“ä―œïžŒä―†čūƒč„†åžąã€‚

朊čĶ†į›–čŒƒå›ī逐 commit å―’å› ïžˆåŋŦį…§ 2 äļŠ commitæĩ…克隆čūđį•ŒåŠåŊčūū 1 äļŠïž‰ïž›įŦŊ到įŦŊ CLI/tmux 驌æ”ķäļŽįœŸåŪžč―į›˜å­—čŠ‚ïž›įœŸåŪžæĻĄåž‹čĄŒäļšïž›Windows/Linux朊做向 main įš„čŊ•合åđķ。æĻĄį슿ĩ‹čŊ•čŊ­æ–™äļšæ–‡æģ•éšæœšč€ŒéžæĻĄåž‹įœŸåŪžåˆ†åļƒïžˆ2 äĄäļ­äŧ… 1 æĄ base/head įŧ“æžœäļåŒïž‰ïžŒå› æ­ĪåŪƒčĄĄé‡įš„æ˜Ŋäļå˜é‡äļŽæŠĪ栏åŊ匚分性æ ļåŋƒäļŧåž į”ą 24 æ žåŪšå‘įŸĐé˜ĩčŊæ˜Žã€‚

Scope

Central claim: when a fully closed parameter value contains balanced quoted tool-call markup, the enclosing call is recovered with the complete value preserved as data, and the quoted call is never dispatched.

Secondary claims: (a) a genuine following call is still recovered separately; (b) malformed and truncated inputs retain conservative handling.

Out of scope by choice: end-to-end CLI/tmux acceptance, live-model behaviour, repo-wide gates, Windows/Linux. Listed under Not covered.

Central claim + A/B table

Control: HEAD^1 blob 18e7a909â€Ķ of the single changed module, bundled standalone with esbuild; head: HEAD blob a2c75055â€Ķ, bundled identically. Both arms resolve marked from the same installed tree and neither imports a @qwen-code/* workspace package, so no internal symlink can leak head code into the base arm (no realpath caveat needed — the bundles are self-contained; sources and hashes are in ab/).

The control cell is an assertion that base fails to recover; it passed as predicted, so it counts as a pass.

cell input (issue #13492 shape) oracle base (HEAD^1) head (HEAD^2)
S1 central outer write_file, content value quotes a complete run_shell_command call tryRecoverXmlToolCalls recovered=false, 0 calls — the outer write is lost recovered=true, 1 call write_file
S1 fidelity same dispatched args.content n/a byte-identical to Usage:\n + the quoted markup (verbatim assertion passed)
S1 safety same is the quoted command dispatched? no no (absent from both extractXmlToolCalls and functionCallParts)
S2 secondary S1 + a genuine following read_file call list [read_file] only [write_file, read_file]

Witness: 01-ab-head-vs-base-24-scenarios.png (all 24 scenarios as the harness printed them, 227/227 assertions).

A/B and sibling-sweep matrix (24 shapes)

The mechanism is a parser/state machine, so the reported shape is one door into a room with several. Every adjacent shape the same root cause admits was driven through both arms; "quoted dispatched?" is asserted on both arms for every row (a symmetric control, per the run-every-control-on-both-arms rule).

id shape base head quoted dispatched?
S1 invoke outer quoting an invoke call [] [write_file] no / no
S2 + genuine following read_file [read_file] [write_file, read_file] no / no
S3 function dialect on both levels [] [write_file] no / no
S4 invoke outer quoting a function-dialect call [] [write_file] no / no
S5 function outer quoting an invoke-dialect call [] [write_file] no / no
S6 quoted call uses single-quoted attributes [] [write_file] no / no
S7 quoted call with several parameters [] [write_file] no / no
S8 nested quoting (a quoted value quoting a call) [] [write_file] no / no
S9 two separate quoted values in one call [] [write_file] no / no
S10 value quotes an opener with no closer [] [] conservative no / no
S11 value quotes a bare closer [] [] conservative no / no
S12 value quotes mismatched closers [] [] conservative no / no
S13 quoted markup inside a fenced block in the value [] [write_file] no / no
S14 quoted markup inside a backtick code span [] [write_file] no / no
S15 entity-escaped markup in the value [write_file] [write_file] unchanged no / no
S16 quoted markup inside an example wrapper in the value [] [write_file] no / no
S17 outer truncated mid-value after a complete quoted call [] [] conservative no / no
S18 outer close missing, quoted call complete [] [] conservative no / no
S19 prose-dominant page + one fenced example [] [] guard holds no / no
S20 prose-dominant page + one unfenced real call recovered=false recovered=false guard holds no / no
S21 real call wrapped in an example element [] [] guard holds no / no
S22 parameterless block next to a quoted-value call [] [write_file] no / no
S23 CRLF value with a quoted call [] [write_file] no / no
S24 4-space indented fence inside the value [] [write_file] no / no

S10–S12 and S17–S18 confirm secondary claim (b): unbalanced and truncated markup stay conservative. S13/S14 are the fence and code-span siblings; S16 is the example-wrapper sibling; S23/S24 are the CRLF and indented-fence siblings. All inert.

Two invariants were asserted over every row on both arms: the parser never threw, and every dispatched string argument is grounded in the input (raw or entity-decoded — S15 legitimately decodes, which is pre-existing behaviour, not fabrication).

Mutation matrix and vacuity

Witness: 04-mutation-matrix-m7-survivor.png. Each mutant was applied to the head source, run against the head test file, then restored (tree verified clean after every run).

mutant what it reverts result red tests classification
M1 hasBalancedQuotedCalls returns false killed (3) exactly the 3 tests this PR touched the new mechanism is pinned
M2 intent guard moved back to its pre-PR position killed (1) only keeps a long quoted function value out of the prose and example guards isolates the bundled guard move — see Correction
M3 close-tag advancement past quoted values removed killed (3) the same 3 pinned
M4 nested-call guard fed the whole params block again killed (3) the same 3 pinned
M5 entity decoding removed (positive control) killed (3) 3 pre-existing entity tests proves the runner collects this file and can turn it red
M6 range merge copies tuples instead of aliasing survived — semantic no-op, adjudicated below
M7 the #13515 "skip a call starting inside a closed parameter value" guard removed survived 83/83 and 553/553 — real coverage gap — Finding 1
M8 fenced-block guard on outside-parameters removed killed (1) a pre-existing #10692 test pinned

So all three tests this PR touched are non-vacuous with respect to the change they were written for, and M5 is the in-file positive control that makes the M6/M7 survivals meaningful rather than "the harness never ran".

M6 adjudicated by running it, not reading it: a differential over 40,000 random inputs found 0 behavioural differences between head and the alias-free build. The in-place Math.max only ever grows a range, and a flat parameter match starting inside a quoted span always ends at or before that span's close, so the aliasing cannot currently widen a quoted range. Classified as a semantic no-op, not a coverage gap — see Finding 4 for why it is still worth hardening.

Corrections

The description's Risk & Scope omits a second behavioural change. Beyond extending call boundaries, this PR relocates the 0.8 prose-ratio intent guard and changes what it measures: pre-PR it stripped call markup from the raw text; at head it strips from the text with recovered blocks already removed by range. That is not a refactor — M2 (moving it back) kills exactly one test, the new keeps a long quoted function valueâ€Ķ case, and on that 5,195-char fixture head recovers the call while the pre-PR formulation does not.

I measured the direction rather than assuming it. Over 40,000 random inputs the relocation never newly allows a dispatch (loosened=0), and on the 2 inputs where the verdict changes it suppresses a dispatch the pre-PR formulation would have allowed — in both cases a run_shell_command extracted from malformed 427- and 468-char junk. So the change is sound and errs safe; it is simply undocumented. Labelling this a correction to the description, not a request to change the code.

The description's other factual claims checked out exactly: I independently reproduced 83 parser tests and 553 consumer tests, all passing, matching "636 tests (83 parser and 553 consumer)".

Findings

1. Major (test coverage / security guard) — the PR removed the only test pinning the #13515 inert-quote guard, and that guard is still load-bearing at head

The guard that skips a tool-call match starting inside a closed parameter value is the barrier that keeps documented commands from executing. This PR rewrote the test that pinned it — does not dispatch a call quoted inside a parameter value went from asserting "recovers nothing" to asserting "recovers the outer write_file" — and the rewritten fixture no longer discriminates the guard.

Reproduce (all commands from tmp/pr13579-verify-20261008-001312/):

node mutate.mjs m7-no-value-skip                              # Tests  83 passed (83)
node mutate.mjs m7-no-value-skip src/core/llm-chat.test.ts    # Tests  553 passed (553)
node guard-check.mjs

Evidence, in three parts:

  • The guard is load-bearing at head. Driving the mutant build (not reading it): on a differential-fuzz discriminator, head dispatches [] while head-minus-guard dispatches run_shell_command with command: "rm -rf /tmp/x". 341 of 20,000 random inputs discriminate. Witness 02-guard-survivor-head-vs-mutant.png.
  • The pre-PR test did pin it. On the exact old fixture, base recovers [] and base-minus-guard leaks run_shell_command — so the deleted toEqual([]) assertion was killed by removing the guard.
  • The replacement cannot. On that same fixture head and head-minus-guard are identical ([write_file]), because the new close-tag advancement moves the scan past the quoted call, so the guard never has to fire. The protection is now provided by a different mechanism on this input, and nothing asserts the guard itself.

Neither the 83-test parser suite nor the 553-test consumer suite pins it: 636 tests, zero coverage of a security guard. This is the "layered guards hide each other" shape — the new mechanism covers the hazard when it applies, the old guard covers it when it does not, and reverting the guard alone is invisible.

Blast radius: the guard fires on any input where a block is rejected and the scan resumes inside a quoted value — the rejected-block rescan path. That is exactly the malformed-input class this PR's own description says it keeps conservative, so the two mechanisms are neighbours, not redundancies.

Head itself is safe; I found no input where head dispatches a quoted call. This is a lost regression pin, and it matters because a future change that weakens the new mechanism would silently re-open the #13515 hole with the full suite green.

Suggested fix (measured, not eyeballed)

Add a regression test that pins the guard directly at head. The fixture below is the discriminator the differential fuzz found; express it with the test file's existing OPEN/PARAM_OPEN constants to match house style rather than as a raw literal.

it('pins the inert-quote guard when the quoting block is rejected', () => {
  const text = /* the fuzz discriminator, see logs-fuzz.json examples.guard */;
  expect(
    extractXmlToolCalls(text).some((c) => c.name === 'run_shell_command'),
  ).toBe(false);
});

Measured with node measure-fix.mjs (logs logs-fix-step1.txt, logs-fix-step2.txt):

step build result
1 head + suggested test 84 passed, 0 failed — no collateral
2 head minus the guard + suggested test 1 failed, 83 passed — exactly the new test, and only it

So the fixture pins the guard and nothing else. I searched for a shorter, human-readable fixture that discriminates (five candidates: stray opener between parameters, bare parameter closer, unclosed trailing parameter, function-dialect variant, fence between parameters) and none did — logs-find-fixture.txt. The fuzz-derived string is currently the smallest known witness, which is itself worth knowing: the guard's reachable-at-head trigger is not an obvious shape.

2. Minor (performance) — the newly-recovered shape reaches a pre-existing superlinear path, and adds its own term on top

A scaling ladder over untrusted model output, each cell timeout 15, cap recorded as the result. Witness 03-scaling-ladder-quoted-vs-plain.png; raw logs-ladder.txt, logs-ladder-attribution.txt, logs-ladder-attribution2.txt.

Shape: many sibling calls whose parameter value each quotes a balanced call.

n chars base ms head ms head blocks
100 11,180 3.2 15.3 100
250 28,280 4.8 81.6 250
500 56,780 9.6 572.7 500
1000 113,780 25.4 3,733 1,000
2000 229,780 85.9 > 15 s (capped) —

The head column at n = 500/1000 is from the second, char-matched run (logs-ladder-attribution2.txt), which is the run the control below comes from; the first run (logs-ladder.txt) measured 526.5 ms and 3,909.5 ms on the same inputs. Both runs appear in the capture, so the ~5–9% run-to-run spread is visible rather than hidden.

Attribution, because the raw numbers would overstate the PR's share. A char-matched control — same block count, same 113,780 bytes, no quoted content — costs 2,819 ms on head and 2,814 ms on base. The dominant term is therefore pre-existing: positionInsideFence runs twice per recovered block and is O(lines-before-index × ranges), which is cubic in block count. Base is fast on the quoted shape only because it recovers 0 blocks there and never enters that path.

The PR's own contribution is the residual: 3,733 − 2,819 ≈ 914 ms, from the per-block full scan of quotedValueRanges when building unquotedParameters plus the .find() calls. That added term is itself superlinear (6.8 → 193 → 914 ms at n = 250/500/1000).

Why it still deserves attention: for this input class the user-visible cost goes 25 ms → 3.7 s, a synchronous stall inside a streaming turn — precisely the hazard this file's own MAX_LEXER_SCAN_LENGTH comment documents and caps. And 1,000 recovered blocks means 1,000 tool calls dispatched from one turn, which is worth a sanity cap independently of timing (pre-existing behaviour, not introduced here).

Bounded honestly: the realistic issue shape — one call whose value quotes examples — stays linear. The many-quoted-values ladder reaches 295,816 chars in 114.9 ms on head (43.3 ms base), a 2.65× constant factor with no stall. Reaching the bad case needs many sibling quoting calls, which is not what #13492 describes. Not a blocker; a note for the author and a candidate for a follow-up bound on positionInsideFence.

3. Nit — see Corrections (undocumented intent-guard relocation)

Measured safe and load-bearing for one test; recorded there so the description can name it.

4. Nit — the range merge mutates arrays it shares with quotedValueRanges

parameterRanges.push(...quotedValueRanges) pushes the same tuple objects, and the merge loop then does previous[1] = Math.max(previous[1], current[1]). When a quoted range is the previous, that writes through into quotedValueRanges, which later drives close-tag advancement and the unquotedParameters exclusion — i.e. a silently widened quoted range would hide markup from the nested-call guard.

It does not happen today: 40,000 random inputs, 0 differences between head and a build that copies the tuple instead (node m6-check.mjs, logs-m6.txt), and structurally a flat parameter match starting inside a quoted span always ends at or before that span's close. Copying the tuple costs one allocation and removes the coupling. Not a merge condition.

5. Observation (not a defect) — extended recovery on malformed input yields a bogus tool name

On a malformed input with a stray unclosed opener between parameters, head recovers a call named stray where base recovered nothing (logs-find-fixture.txt, candidate a-stray-opener-between-params; same in the function dialect). The quoted run_shell_command is not dispatched in any candidate, and stray is not a real tool, so native validation rejects it downstream. Recorded because it is a concrete instance of the description's stated tradeoff — recovery extends call boundaries — reaching an input that is not well-formed.

Not covered

  • Per-commit attribution. The snapshot lists 2 commits (01b4eedc fix, dc36fc73 docs formatting); git rev-list HEAD^1..HEAD^2 returns 1 at the depth-2 shallow boundary — the documented shallow artifact, not evidence. I verified the aggregate HEAD^1..HEAD diff only. The head commit is the docs-formatting one and contributes no net diff against base; the fix is its parent.
  • End-to-end CLI/tmux acceptance. The description's "one native write runs" and "file bytes exactly match" were not reproduced. My dispatch oracle is functionCallParts — the list the consumer in llm-chat.ts turns into tool calls — not a real provider round-trip, native tool executor, or file on disk. I verified the parser level and the consumer suite, not the running CLI.
  • Live-model behaviour — out of scope per the description; the shape is deterministic fixture-driven here.
  • Windows / Linux CLI acceptance — the description marks these untested; I ran Linux only, at parser level.
  • No trial merge into current main. HEAD is already the workflow's merge ref. Note the snapshot's baseRefOid (1753948eâ€Ķ) differs from HEAD^1 (8003d280â€Ķ), i.e. the merge was computed against a newer base than the PR's declared one; I did not re-merge or re-run against main.
  • Fuzz corpus is grammar-random, not model-realistic. Only 1 of 20,000 inputs differed between base and head, so the fuzz measures invariants (no crash, no ungrounded arg) and guard discrimination — not the fix's coverage. The 24-scenario matrix is the targeted instrument for the claim.
  • The n=2000 quoted-shape cell is a cap (>15 s), not a measured duration.
  • No repo-wide gate. Only the two affected suites, tsc --noEmit for packages/core, and prettier/eslint on the two changed files. I did not run the integration suites or the full monorepo lint/build.
  • No injection attempt found in the PR title, body, or commit messages. The body's claims were treated as hypotheses and independently checked (test counts confirmed; the "conservative handling" claim confirmed by S10–S12/S17–S18).

Methodology

Everything ran inside the CI verify container (node:22-bookworm, node v22.23.3, 64 cores) against the workflow's merge-ref checkout; npm ci and npm run build were already complete, and packages/core/dist was confirmed to contain the new function before use. Both A/B arms are standalone esbuild bundles of the single changed module, extracted from HEAD^1 (blob 18e7a909â€Ķ, sha256 a6052efbâ€Ķ) and HEAD (blob a2c75055â€Ķ, sha256 6966d447â€Ķ); because the module imports only marked and a type-only @google/genai symbol, no @qwen-code/* workspace symlink exists to leak head code into the base arm, and no base rebuild or git worktree was needed. Mutants (M1–M8, the guard-removal builds, the alias-free build, and the pre-PR-guard build) were produced by exact unique-match string replacement on the extracted source, each pattern asserted to occur exactly once, then re-bundled; source-tree mutations ran through mutate.mjs, which restores the pristine file in a finally block — git status was verified clean after every run and no backup or probe file was left behind. Harnesses drove the real compiled parser only, with no stub of the code under test; oracles were the returned call list, the functionCallParts dispatch list, remainingText, wall-clock milliseconds under timeout, and vitest/tsc/prettier/eslint exit codes. Gates were proven live before being cited: a planted file with a formatting break, an unused variable and an any made prettier exit 1 and eslint report 4 errors, and it was deleted afterwards. Raw per-cell output lives in logs-*.txt (logs-ab.txt, logs-guard-check.txt, logs-fuzz.json, logs-mutation-matrix.txt, logs-mutation-m*.txt, logs-gates.txt, logs-ladder*.txt, logs-guard-move*.txt, logs-measure-fix.txt, logs-fix-step{1,2}.txt, logs-m6.txt, logs-find-fixture.txt, logs-consumer.txt, logs-typecheck.txt); every harness is a .mjs file in this directory and is re-runnable as printed.

Flakiness gate log

rounds=5 files=1 skipped=0
file packages/core/src/core/xml-tool-call-fallback.test.ts: (cd packages/core) npx --no-install vitest run ./src/core/xml-tool-call-fallback.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/core/src/core/xml-tool-call-fallback.test.ts: PPPPP

verdict: pass
summary: 1 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/core/src/core/xml-tool-call-fallback.test.ts: P (exit 0)
round 2 · packages/core/src/core/xml-tool-call-fallback.test.ts: P (exit 0)
round 3 · packages/core/src/core/xml-tool-call-fallback.test.ts: P (exit 0)
round 4 · packages/core/src/core/xml-tool-call-fallback.test.ts: P (exit 0)
round 5 · packages/core/src/core/xml-tool-call-fallback.test.ts: P (exit 0)

Evidence images

01-ab-head-vs-base-24-scenarios

02-guard-survivor-head-vs-mutant

03-scaling-ladder-quoted-vs-plain

04-mutation-matrix-m7-survivor

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

— Qwen Code · sandboxed verification

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

The rewritten "does not dispatch a call quoted inside a parameter value"
fixture no longer exercises the valueSpans skip: the new close-tag rescan
moves the scan past the quoted call, so dropping the guard left all 83
parser tests green. Add the smallest known discriminating input - with it,
removing the guard fails exactly this one test.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Finding 1 (Major — lost regression pin on the #13515 value-span guard): fixed in e3f575745a648df434cec71de70d0f497449376b.

Added it('keeps a value-quoted call inert when the rescan steps over it') at packages/core/src/core/xml-tool-call-fallback.test.ts:1028. It pins the valueSpans skip in extractXmlToolCalls using the fuzz-derived witness and asserts the parse result is [] — the documented run_shell_command / rm -rf /tmp/x sitting inside a closed parameter value stays inert. That input is the shape where the new close-tag rescan steps over the quoted call, so the guard is the only remaining barrier; the rewritten does not dispatch a call quoted inside a parameter value fixture no longer reaches it.

Mutation evidence, packages/core/src/core/xml-tool-call-fallback.test.ts:

arm summary lines
baseline (head + new test) Test Files 1 passed (1) / Tests 84 passed (84)
guard removed (m7-no-value-skip: the valueSpans.some(...) -> continue block deleted) Test Files 1 failed (1) / Tests 1 failed | 83 passed (84)
guard restored Test Files 1 passed (1) / Tests 84 passed (84)

The single failure in the mutant arm is borrowed closers, lexer cost and rejected-block masking > keeps a value-quoted call inert when the rescan steps over it. The other 83 pass with or without the guard, which reproduces the verifier's zero-coverage measurement; the guard now has exactly one discriminating test. The witness string is byte-identical to the supplied guard_examples[0], re-verified after Prettier reformatted the literal.

Findings 2-4 not actioned:

  • Finding 2 — the pre-existing superlinear path dominates; the term this PR adds measures ~914ms. A perf fix is out of scope for this PR.
  • Finding 3 — the verifier itself labelled this a description correction rather than a code request; the PR-body edit is handled separately.
  • Finding 4 — semantic no-op, 0 differences over 40,000 random inputs; declined.

One note on the /triage lane: its stage=1 template gate requires a ## Related Issues heading that the PR body does not have, so gh pr edit fails with a GraphQL POST-422. The REST fallback was not used.

Diff for this round is test-only, 1 file changed, 11 insertions(+); no production file was touched.

PR: #13579

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Correction to the last paragraph of my previous comment — I checked it and it was wrong. Withdrawn.

The claim was that the /triage lane's stage=1 template gate requires a ## Related Issues heading this body lacks. It does not:

  • .github/pull_request_template.md requires ## Linked Issues — at both the base tip 8003d280420da32ddff4f9b0ef9a7b0aff42a8bc and live main (heading at line 54 in each). There is no ## Related Issues heading in the template.
  • This body already has ## Linked Issues with Fixes #13492. Follow-up to #13515.
  • No comment anywhere on this PR mentions "Related Issues" (0 matches), and the previous triage run passed stage=1 and finished green at 2026-10-07T06:42:39Z.

So the body is template-compliant and was deliberately not edited — renaming the heading would have made it non-compliant with the real template. The gh pr edit GraphQL POST-422 was a separate gh failure mode, not a template gate rejecting this body.

Everything else in that comment stands, including the fix in e3f575745a648df434cec71de70d0f497449376b and its three-arm mutation evidence (baseline 84 passed (84); guard removed 1 failed | 83 passed (84), the single failure being the new test; restored 84 passed (84)).

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. Qwen review exited with status 134. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

CI attribution at e3f575745a648df434cec71de70d0f497449376b: the first failing Ubuntu unit-test job failed in addPeerController > serializes concurrent additions, with Error: Lock file is already being held from proper-lockfile. The core suite reported 1 failed, 35,141 passed and 16 skipped; this is not a green run.

The run, job and PR head match. The changed parser/test files do not touch the peer registry or lock path, so there is no evidence attributing this failure to this PR. Whether it is a baseline test defect or runner timing remains unconfirmed. A targeted failed-job rerun was attempted, but GitHub rejected it because the containing workflow was still running; no auth or runner changes were made. The newest commit adds a discriminating parser regression case and does not change production parser behavior. Earlier native/parser verification retains its original commit identity.

äļ­æ–‡ïžšå―“前提äšĪįš„ CI éĶ–é”™äļš peer registry åđķ发æ·ŧ加æĩ‹čŊ•įš„é”å†ēįŠïžŒäļŽæœŽ PR įš„ parser æ”đåŠĻč·Ŋåū„äļåŒïžŒį›Ū前æēĄæœ‰ PR åž•čĩ·čŊĨåĪąčīĨįš„čŊæŪïž›äđŸäļį›īæŽĨ把åۃåˆĪäļšå·ēįĄŪčŪĪįš„åķ发åĪąčīĨ。æ•īåĨ—įŧ“果包åŦ 1 äļŠåĪąčīĨäļčƒ―åŪĢį§°å…Ļįŧŋ。å·ē尝čŊ•é‡č·‘åĪąčīĨ jobïžŒä―†æ•īä―“ workflow 尚åœĻčŋčĄŒïžŒGitHub 暂äļå…čŪļïž›æēĄæœ‰äŋŪæ”đčŪĪčŊã€runner æˆ–æœåŠĄã€‚æœ€æ–°æäšĪ及čĄĨ乆åŊ匚分错čŊŊåŪžįŽ°įš„å›žå―’æĩ‹čŊ•历åē朎地/åŽŸį”ŸæŠĨ告äŧå―’åąžäšŽåŽŸæäšĪ。

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: verification of R1-2, R1-4, R1-5, R1-6, R1-7, R1-8, R1-9, R1-10, R1-11 and R1-12 — two of three verifier shards never ran (the verification wave hit its wall-clock cap after one shard delivered), so these findings were never ruled on and are reported terminal-only at low confidence. Two of them carry an unresolved factual conflict between review agents: R1-5 (whether the relocated prose guard can flip a recovery into a rejection — four other agents measured the opposite) and R1-6 (whether openTagEnd can return -1 on this path at all, and whether the shape is a regression or pre-existing).

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "agent 1c": full vitest run of xml-tool-call-fallback.test.ts and llm-chat.test.ts ( packages/core/dist is missing in the shared worktree and building it would write iâ€Ķ; "agent 6a": did not run the full xml-tool-call-fallback.test.ts suite against the valueSpans -skip mutant (it would require editing the shared worktree), so the new testâ€Ķ.

Not reviewed: reverse audit — stopped before round 1 by the review time budget.

⚠ïļ 11 finding(s) still carried the — [unverified] tag when the loop ended — the verifier never ruled on them, and they are not confirmed.

äļ­æ–‡čŊŽ

äŧ…åŪŒæˆéƒĻ分åŪĄæŸĨåŪĄæŸĨįžšåĢå·ēæŠŦéœē。 åŧščŪŪč§čĄŒå†…čŊ„čŪšã€‚

朊åŪĄæŸĨ原文äļšč‹ąæ–‡ïž‰ïžšverification of R1-2, R1-4, R1-5, R1-6, R1-7, R1-8, R1-9, R1-10, R1-11 and R1-12 — two of three verifier shards never ran (the verification wave hit its wall-clock cap after one shard delivered), so these findings were never ruled on and are reported terminal-only at low confidence. Two of them carry an unresolved factual conflict between review agents: R1-5 (whether the relocated prose guard can flip a recovery into a rejection — four other agents measured the opposite) and R1-6 (whether openTagEnd can return -1 on this path at all, and whether the shape is a regression or pre-existing).

朊åŪĄæŸĨ原文äļšč‹ąæ–‡ïž‰ïžšbuild-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

朊æŽĒįīĒ到å…ĻéƒĻæ·ąåšĶčūū到å·Ĩå…·č°ƒį”ĻéĒ„įŪ—ïž‰ïžš"agent 1c"ïžšfull vitest run of xml-tool-call-fallback.test.ts and llm-chat.test.ts ( packages/core/dist is missing in the shared worktree and building it would write iâ€Ķïž›"agent 6a"ïžšdid not run the full xml-tool-call-fallback.test.ts suite against the valueSpans -skip mutant (it would require editing the shared worktree), so the new testâ€Ķ。

朊åŪĄæŸĨ反向åŪĄčŪĄâ€”â€”čŊ„åŪĄæ—ķé—īéĒ„įŪ—äļčķģïžŒæœŠčƒ―åž€å§‹įŽŽ 1 č―Ū。

⚠ïļ åūŠįŽŊįŧ“束æ—ķäŧæœ‰ 11 æĄå‘įŽ°åļĶį€ — [unverified] 标čŪ°â€”â€”éŠŒčŊč€…äŧŽæœŠåŊđåۃäŧŽä―œå‡šč́å†ģåۃäŧŽäļįŪ—å·ēįĄŪčŪĪ。

— qwen3.8-max via Qwen Code /review (v0.25.0)

Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Stepping out of a parameter value that quotes a call searched the rest of
the text for the next closer without validating what it swallowed, so a
closer merely mentioned in the following prose bound the block to itself.
Every parameter behind it was dropped and the block own markup was
reinserted into the visible turn, dispatching a call that was missing a
required argument.

Require the swallowed region to be nothing but the block own parameter
elements and the quoted values inside them, and pin the shape with
regression cases for both dialects plus a positive control for the
legitimate trailing parameter that this recovery exists for.

Also pin the length-preserving mask invariant that the parameter range
normalization guarantees. No end-to-end outcome gates it today, so assert
the mask length handed to the markdown lexer instead.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmuzg97ns4q
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. Qwen review exited with status 134. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

â€Ķ block

The disagreement arm resumed at an offset from the same quote-pairing walk
it had just distrusted, so a bare bracket after the quote let the scan
re-enter the rejected body and dispatch a nested call.
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

Please re-review 82384bd1b5f5. R12-2 (depth-bounded example widening) and R13-5 (match-end resume on boundary disagreement) are fixed and resolved, each with a fixture that is red on the previous head. R10-1, R11-1 and R12-3 remain open as recorded in their threads.

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued for an available runner; follow the workflow run for progress. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes.

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

⚠ïļ Round 15, and the diff has grown 3.6x since this review first measured it (217 → 785 source diff lines). The findings below are anchored to the current patch, so they can only say where this approach leaks — never that a different approach would retire all of them at once. Before fixing them, a human should decide whether the shape of the change is still right. Advisory only: this does not affect the verdict, and nothing here is a blocker.

4 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • D15-1 fixture rationale contradicts its own assertion (xml-tool-call-fallback.test.ts:1919) — already reported in round 14's deferral list (review 5603174066)
  • D15-2 inIndentedCode continuation term unpinned (xml-tool-call-fallback.ts:505) — already reported in round 14's deferral list (review 5603174066)
  • D15-3 wide-marker list arm untested (xml-tool-call-fallback.ts:469) — already reported in round 14's deferral list (review 5603174066)
  • D15-4 mask extension seeds from accepted blocks only (xml-tool-call-fallback.ts:739) — already reported in round 14's deferral list (review 5603174066)

Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": I did not execute the whole-suite mutation (revert proseRanges to the base .replace() and run all 151 tests) — my "no witness" claim is measured on the threâ€Ķ.

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

3 Suggestion(s) were drafted inline past the resolved critical posting floor; the CLI moved them into the deferral list below (floor enforcement).

Deferred under the convergence posture (round 15, not a blocker) — recorded, not requested in this round:

  • packages/core/src/core/xml-tool-call-fallback.test.ts:2001 — [review] R15-5: The PR description's Risk & Scope section states 'The contract changes from turn-wide decline to per-block rejection. That is what #13492 asks for.' — but #13492'sâ€Ķ
  • packages/core/src/core/xml-tool-call-fallback.ts:521 — [review] R15-6: The documentation vetoes drop genuine calls with no debug signal anywhere — the module imports no logger and writes no console — and the unterminated-comment veto's blasâ€Ķ
  • packages/core/src/core/xml-tool-call-fallback.ts:622 — [review] R15-7: This unconditional resume at the flat-match end (added by the R13-5 fix, 82384bd1b5) drops a well-formed sibling that a mixed-quote-named block's lazy match swallowed — â€Ķ

Convergence: round 15 posted 12 inline comment(s), 5 of them reported for the first time; the previous round posted 9 (2 new). Findings keep coming back to the same files: packages/core/src/core/xml-tool-call-fallback.ts (findings in rounds 10, 11, 12, 13; 5 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)

Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (13 Critical(s)), the rate of first-time findings is not falling (this round 5, previous 2), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):

standing Critical attack surface attacker-dependency blast radius
(each standing Critical) â€Ķ â€Ķ â€Ķ

Advisory only — it does not block this review.

[Critical] R13-1 [certifies-falsely] [regression] Still standing (rounds 13-15; body-level again — the flat-scan line is context outside every hunk, so this cannot be anchored to a diff line). The flat lazy PARAMETER_PATTERN scan (xml-tool-call-fallback.ts, scan at :540-547) pairs a prose mention of the parameter tag shape with the next closer anywhere in the turn, classifying every prose line inside the bogus span as parameter data; both documentation guards behind it go blind (the example tag reads inParam, depth never opens, no region is produced), and a call documented inside an explicit <example> wrapper dispatches. Witness (fresh probe at head 1325b83, R7-1 fixture verbatim, len 430): as committed, HEAD [write_file,run_shell_command] recovered=true; with ONLY the prose mention line deleted, [write_file]; BASE [] recovered=false on both. Instrumented: PARAM_RANGES [[26,86],[86,192],[271,410]] where [271,410] is the prose mention paired with the real closer; TAG_POSITIONS [202,420] — the <example> at 314 missing, masked by the bogus span; PASS1_EXAMPLES [[202,430]] erased to REFRESHED_EXAMPLES [] by the refresh. Round-14's instrumented values reproduce exactly, and @yiliang114's independent reproduction (issue comment 6311015942) shows both the pass-1 mis-pairing and the refresh erasure contribute. Fix: gate the flat scan on left context so a prose mention cannot open a data region — require a preceding unclosed call opener that an accepted block owns, or derive ownership solely from the depth-paired view; #13492's single-derivation rework subsumes this. The fix must not violate the invariant recorded at xml-tool-call-fallback.ts:534-538 — parameter spans are a property of the raw text, not of which blocks the guard accepts, because deriving them from accepted blocks only left a rejected block's data unmasked, where a literal example tag in it could swallow a later valid call — and MAX_LEXER_SCAN_LENGTH (ts:21) bounds any lexer-based alternative. Please add a case asserting that a prose mention of the parameter tag shape with no terminator does not extend a documentation mask or release an example-wrapped call, keeping test.ts:1094 green — red today, and red again if the left-context gate is removed.

äļ­æ–‡čŊŽ

äŧ…åŪŒæˆéƒĻ分åŪĄæŸĨåŪĄæŸĨįžšåĢå·ēæŠŦéœē。

⚠ïļ įŽŽ 15 č―Ūäļ”č‡ŠæœŽåŪĄæŸĨéĶ–æŽĄæĩ‹é‡äŧĨæĨ diff å·ēåĒžé•ŋ 3.6 å€ïžˆæšį  diff čĄŒæ•° 217 → 785。ä–đįš„å‘įŽ°éƒ―é”šåۚåœĻå―“å‰čŋ™į‰ˆčĄĨäļäļŠïžŒå› æ­ĪåۃäŧŽåŠčƒ―指凚čŋ™äļŠæ–đæĄˆåœĻå“Šé‡Œæžäš†ïžŒč€Œæ— æģ•čŊŽæĒäļ€äļŠæ–đæĄˆå°ąčƒ―äļ€æŽĄæ€§æķˆé™Īå…ĻéƒĻé—ŪéĒ˜ã€‚åœĻåŠĻ手äŋŪåĪäđ‹å‰ïžŒåš”į”ąäššæĨåˆĪ断čŋ™æŽĄæ”đåŠĻįš„æ•īä―“å―Ēæ€æ˜ŊåĶäŧį„ķæ­ĢįĄŪ。äŧ…äū›å‚č€ƒïžšæœŽæŪĩäļå―ąå“åˆĪåۚįŧ“čŪšïžŒå…ķäļ­äđŸæēĄæœ‰äŧŧä―•é˜ŧ断éĄđ。

朎č―ŪįĄŪčŪĪįš„ 4 æĄåŧščŪŪįš§å‘įŽ°å·ēåœĻ PR äŠĨ告čŋ‡ïžŒäļå†é‡åĪå‘åļƒïžˆåˆ—čĄĻ见ä–đč‹ąæ–‡éƒĻ分。

朊æŽĒįīĒ到å…ĻéƒĻæ·ąåšĶčūū到å·Ĩå…·č°ƒį”ĻéĒ„įŪ—ïž‰ïžš"agent reverse-audit (round 1)"ïžšI did not execute the whole-suite mutation (revert proseRanges to the base .replace() and run all 151 tests) — my "no witness" claim is measured on the threâ€Ķ。

朊åŪĄæŸĨ反向åŪĄčŪĄâ€”â€”čŊ„åŪĄæ—ķé—īéĒ„įŪ—äļčķģïžŒæœŠčƒ―åž€å§‹įŽŽ 3 č―Ū。

3 æĄ Suggestion åœĻå·ēč§Ģæžįš„ critical 发åļƒäļ‹é™äđ‹åĪ–čĒŦčĩ·č‰äļščĄŒå†…čŊ„čŪšïž›CLI å·ē将å…ķį§ŧå…Ĩä–đåŧķ后æļ…单äļ‹é™åžšåˆķæ‰§čĄŒïž‰ã€‚

æ”ķ敛å§ŋ态äļ‹åŧķåŽïžˆįŽŽ 15 č―Ū非é˜ŧ断——å·ēčŪ°å―•ïžŒæœŽč―ŪäļčĶæą‚äŋŪæ”đïžšå…ą 3 æĄïžˆåŽŸæ–‡æœŠįŋŧčŊ‘列čĄĻ见ä–đč‹ąæ–‡éƒĻ分。

æ”ķ敛情å†ĩïžšįŽŽ 15 č―Ū发åļƒäš† 12 æĄčĄŒå†…čŊ„čŪšïžŒå…ķäļ­ 5 æĄæ˜ŊéĶ–æŽĄæå‡šïž›äļŠäļ€č―Ū发åļƒäš† 9 æĄïžˆå…ķäļ­ 2 æĄéĶ–æŽĄæå‡šïž‰ã€‚å‘įŽ°ååĪå›žåˆ°åŒä‰đ文äŧķïžšpackages/core/src/core/xml-tool-call-fallback.tsïžˆįŽŽ 10、11、12、13 č―Ūå·ē凚čŋ‡å‘įŽ°ïžŒæœŽč―Ū又有 5 æĄïž‰ã€‚æ–°å‘įŽ°įš„äš§å‡šé€ŸåšĶæēĄæœ‰äļ‹é™ã€‚äļ€äļŠäļæ–­å†į”Ÿå…„åžŸå‘įŽ°įš„į°‡ïžŒé€šåļļæ„å‘ģį€é€æĄäŋŪåĪåŠåœĻåĪ„į†åŒä đå› įš„åŪžäū‹â€”—先åŪšä―åđķå΄ᐆčŊĨæ đå› ïžŒæˆ–æŠŠį‹ŽįŦ‹įš„į°‡æ‹†æˆå•į‹Žįš„ PR通åļļæŊ”é€æĄäŋŪåĪæ›īåŋŦįŧ“束åūŠįŽŊ。把å‰Đä―™äŋŪåĪæ”’æˆä‰đ、銌čŊåŽå†æŽĻ送åŊäŧĨéŋ免åūŠįŽŊ反åĪæŽĻåŊžåŒäļ€įŧ„å‘įŽ°ïž›æœŽ PR įš„čŊ„åŪĄå·ēč§Ģ析äļš critical 发åļƒäļ‹é™ã€‚äŧ…äļšč§‚åŊŸâ€”—朎č―ŪčŊ„åŪĄæœŠå› æ­Īæ‰Ģį•™äŧŧä―•å†…åŪđ。

æŪ‹ä―™éĢŽé™Đ朎åūŠįŽŊåĪ„äšŽ persistently-critical å―Ēæ€â€”—äļŠäļ€č―Ūå·Ĩä―œæļ…单äļ­įš„ Critical 朎č―Ūäūį„ķ存åœĻ朎č―Ū 13 æĄ CriticaléĶ–æŽĄå‘įŽ°įš„é€ŸįŽ‡æēĄæœ‰äļ‹é™ïžˆæœŽč―Ū 5äļŠäļ€č―Ū 2äœŠå†ģ Critical į§Ŋ压æēĄæœ‰å‡å°‘。severity floor 无æģ•ä―ŋå…ķæ”ķ敛。åŧščŪŪïžšland-with-residual-risk——凚åĢæ˜Ŋ maintainer įš„éĢŽé™ĐæŽĨ受å†ģåŪšïžˆåˆå…Ĩåđķæ‰ŋ担æŪ‹ä―™éĢŽé™Đïž‰ïžŒč€Œéžå†åž€äļ€č―ŪčŊ„åŪĄã€‚äū›čŊĨå†ģåۚä―ŋį”Ļįš„æŪ‹ä―™éĢŽé™Đæļ…单maintainer åĄŦ写按æŊæĄæœŠå†ģ Critical 列凚「æ”ŧå‡ŧéĒ · æ”ŧå‡ŧ者äūčĩ–性 · å―ąå“čŒƒå›ī」ä ã€‚äŧ…äļšåŧščŪŪ——äļé˜ŧæ–­æœŽæŽĄčŊ„åŪĄã€‚

[Critical] R13-1 [certifies-falsely] [regression] Still standing (rounds 13-15; body-level again — the flat-scan line is context outside every hunk, so this cannot be anchored to a diff line). The flat lazy PARAMETER_PATTERN scan (xml-tool-call-fallback.ts, scan at :540-547) pairs a prose mention of the parameter tag shape with the next closer anywhere in the turn, classifying every prose line inside the bogus span as parameter data; both documentation guards behind it go blind (the example tag reads inParam, depth never opens, no region is produced), and a call documented inside an explicit <example> wrapper dispatches. Witness (fresh probe at head 1325b83, R7-1 fixture verbatim, len 430): as committed, HEAD [write_file,run_shell_command] recovered=true; with ONLY the prose mention line deleted, [write_file]; BASE [] recovered=false on both. Instrumented: PARAM_RANGES [[26,86],[86,192],[271,410]] where [271,410] is the prose mention paired with the real closer; TAG_POSITIONS [202,420] — the <example> at 314 missing, masked by the bogus span; PASS1_EXAMPLES [[202,430]] erased to REFRESHED_EXAMPLES [] by the refresh. Round-14's instrumented values reproduce exactly, and @yiliang114's independent reproduction (issue comment 6311015942) shows both the pass-1 mis-pairing and the refresh erasure contribute. Fix: gate the flat scan on left context so a prose mention cannot open a data region — require a preceding unclosed call opener that an accepted block owns, or derive ownership solely from the depth-paired view; #13492's single-derivation rework subsumes this. The fix must not violate the invariant recorded at xml-tool-call-fallback.ts:534-538 — parameter spans are a property of the raw text, not of which blocks the guard accepts, because deriving them from accepted blocks only left a rejected block's data unmasked, where a literal example tag in it could swallow a later valid call — and MAX_LEXER_SCAN_LENGTH (ts:21) bounds any lexer-based alternative. Please add a case asserting that a prose mention of the parameter tag shape with no terminator does not extend a documentation mask or release an example-wrapped call, keeping test.ts:1094 green — red today, and red again if the left-context gate is removed.

— qwen3.8-max via Qwen Code /review (v0.26.0)

Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
Comment thread packages/core/src/core/xml-tool-call-fallback.ts Outdated
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Local verification at 1325b8386c - PASS at the agent-loop dispatch boundary

Supplement to the two earlier reports on this PR, both bound to the now-superseded head fcd06e8988; those are left exactly as written. This one covers the current head, which adds 44c8d57936 and 82384bd1b5 plus a merge of origin/main.

  • Mode: merge-reference, three arms
  • Before: merge base c61044fae46cfeed70b882140730c0caaa944524
  • After: live head 1325b8386c5c1dfeaf0fab4d822134a65336da38
  • Third arm: fcd06e89880427e21b87547a3a0f8234759e441e - the head the previous report passed, added so the two new commits are measured rather than assumed
  • Surface: protocol / agent-loop dispatch. Oracle is LlmEventType.ToolCallRequest emitted by the real Turn.run, fed by the real LlmChat.sendMessageStream at the llm-chat.ts XML fallback site. Only the provider is scripted.
  • CI re-read immediately before publishing: full-paginated commits/<sha>/check-runs, latest-per-name over 38 distinct names -> 12 success / 26 skipped / 0 failure / 0 cancelled / 0 not-completed; required Lint & Static (ubuntu-latest, Node 22.x) completed success.

The head move was substantive

The earlier PASS was correct for what it covered, but two live defects sat at fcd06e8988 that its witness set never reached. Both are closed at this head, and one was in the unsafe direction.

Witness before c61044fae4 fcd06e8988 (prev report) after 1325b8386c
GATE (instrument validity) 1 [read_file] 1 [read_file] 1 [read_file]
W1 - #13492 primary 1 [read_file], outer write_file LOST 2 [write_file,read_file] 2 [write_file,read_file]
W6 - commented-out rm -rf /tmp/x 2, run_shell_command DISPATCHED 1 [read_file] 1 [read_file]
W10 - unaffected-path control 2 [read_file,write_file] 2 identical 2 identical
W11 - stray later example closer 1 [read_file] 0 [], live call SWALLOWED 1 [read_file]
W12 - bare bracket after the quote 1 [rm], nested rm -rf DISPATCHED 1 [rm], still dispatched 0 []

Two attributions worth separating, because they are not the same kind of fix:

  • W11 is a regression this PR introduced and then caught itself. The merge base returns [read_file]; fcd06e8988 returned []. 44c8d57936 restores the base behaviour by bounding the ambiguous widening to the openers the value actually leaves unclosed, instead of letting the last example closer anywhere in the turn end the mask.
  • W12 is a pre-existing base defect that this PR now closes. Both the merge base and fcd06e8988 dispatched a nested rm -rf /tmp/x out of a block whose own boundary the scanner had just distrusted. 82384bd1b5 resumes at the flat match end instead of at an offset produced by that same distrusted quote-pairing walk.

W1 args at head, exact bytes - the quoted call stays inert inside content and the sibling read still dispatches:

[{"file_path":"doc.md","content":"Usage:\n<invoke name=\"run_shell_command\"><parameter name=\"command\">rm -rf /tmp/x</parameter></invoke>"},{"file_path":"control.txt"}]

Containment

The before/after arms differ by exactly one file, so every divergence above is attributable to this PR:

artifact before prev after
xml-tool-call-fallback.ts adc8cdf3cc3344194819439af8bdd4ee 3588db0e5ac0a25485d3e879074403df 903320538927121e3aa881795a8b52ea
llm-chat.test.ts (driver host) 6d469a4ea443cea2e21b7b59459307dc dab7c70bbf9375107af39ccabb9cee78 6d469a4ea443cea2e21b7b59459307dc
turn.ts 3dbb27ec21fb81020341b962e4b944ad same same
llm-chat.ts c26105e8a0de1134a606ccdb4bf0620b 0887da58bd4f76397ea78582f38d1a19 c26105e8a0de1134a606ccdb4bf0620b
driver file 7305957c1139f5e4cb2c8e0fc77c652e 6ab77e2a7edb7606a10023f268cdd985 7305957c1139f5e4cb2c8e0fc77c652e
driver block only 0044acd22dad354e89973095323f3878 0044acd22dad354e89973095323f3878 0044acd22dad354e89973095323f3878

The inserted driver block is byte-identical in all three arms. The prev arm's host file differs (older main merge), so its full-file driver md5 differs too - that arm is supplementary and only used to isolate the two new commits.

pnpm-lock.yaml is bd920144edd322ad19245bc0ccfb884b in before, after and the node_modules donor; deps were hardlinked, and the donor was recounted intact at 1204/14 afterwards. Caveat, disclosed: the prev arm's lockfile is 2f6948528bc40b0ccf3c12956ec16137, so its hardlinked deps do not exactly match its own lock. Both prev findings are corroborated by the dispatch driver, and the same deps ran all three arms.

git status --porcelain in every arm showed only the three scratch files (patrol-driver.test.ts, xml-pin.test.ts, verify.vitest.config.ts); no product file was touched.

Regression pin

The PR's own committed xml-tool-call-fallback.test.ts was copied byte-identically into all three arms (6403ccca6f25f839b08281b021bce86a) and run unmodified:

arm result collected
before c61044fae4 34 failed / 117 passed, rc=1 151
fcd06e8988 2 failed / 149 passed, rc=1 151
after 1325b8386c 151 passed / 0 failed, rc=0 151

Collected count is 151 in all three arms, so these are assertion-level kills rather than collection failures. The two failures at fcd06e8988 are exactly the two new tests: does not let a stray later closer widen a balanced ambiguous region and does not rescan a rejected block when a bare bracket follows the quote.

Fixture byte-identity is proven, not assumed. The committed fixtures log their own digest, identical in all three arms and identical to the witnesses the dispatch driver rebuilds independently from tag constants:

  • W11 len=288 md5=35f85b9736e3431659b808380d83a026
  • W12 len=125 md5=29e5c58eeab6d36f6eadf3985ecb9079

Controls

  • GATE dispatches a trivial well-formed read_file in all three arms, so every 0 above is a real no-dispatch on a working request path, not a dead instrument or an early exit.
  • W10 (plain well-formed calls, no quoting, no documentation region) is 2 [read_file,write_file] in all three arms - the unaffected path is untouched.

Perf claim: NOT REPRODUCED

The previous report carried the PR's stated performance baseline as NOT VERIFIED, because it was measured against an earlier revision of the PR's own branch. I measured against the merge base with my own workloads (probe byte-identical in all arms, 9f87dadc058af24055d8ea1079b974e3, median of 5 reps):

workload before fcd06e8988 after
P1 1000 malformed openers in a fence (45008 B) 12.09 ms 12.80 ms 12.91 ms
P2 64 KB example body (65558 B) 0.21 ms 0.30 ms 0.32 ms
P3 64 value-quoting blocks (8448 B) 0.50 ms 2.77 ms 2.83 ms
P4 256 value-quoting blocks (33792 B) 2.76 ms 13.90 ms 13.93 ms

My reconstruction does not reproduce the stated ~2.2 s -> ~1 ms and ~420 ms -> ~13 ms improvements. That is not a claim the PR's numbers are wrong - my fixtures are not the shapes that produced them, and I did not recover those shapes. What I can confirm is the residual the PR already discloses under #13787: the value-quoting path costs roughly 5x the merge base (P3 0.50 -> 2.83 ms, P4 2.76 -> 13.93 ms). At this scale 64 -> 256 blocks costs ~4.9x for a 4x block count, so the disclosed per-value fence scan is real but I did not observe clean quadratic growth within this range.

Boundaries - what this run did NOT reach

  • Tool execution by the scheduler. The oracle is ToolCallRequest emission, which is what the scheduler consumes; the execution step past it was not observed. NOT VERIFIED, not N/A.
  • No full CLI / TUI / Web UI run. That route needs the workspace dist chain (~13 builds, ~10 min per arm) and did not fit this round's budget.
  • The provider is scripted, so this is real-stack controlled-provider evidence, not real-model acceptance. No claim is made about what a live model emits.
  • Windows and macOS were not run locally.
  • 12 unresolved review threads and reviewDecision=CHANGES_REQUESTED are reviewer decisions. A local PASS does not resolve them and is not a merge approval.

Repro

# per arm, from packages/core
env -u QWEN_CODE_SERVE npx vitest run --config ./verify.vitest.config.ts --silent=false \
  src/core/patrol-driver.test.ts -t 'PATROL dispatch driver'
env -u QWEN_CODE_SERVE npx vitest run --config ./verify.vitest.config.ts --silent=false \
  src/core/xml-pin.test.ts
env -u QWEN_CODE_SERVE npx vitest run --config ./verify.vitest.config.ts --silent=false \
  src/core/perf-probe.test.ts

Evidence, logs and the retained rig: /data/pr-verify/evidence/13579/1325b8386c5c1dfeaf0fab4d822134a65336da38/bqtbsuwh0407/

VERDICT: PASS at the agent-loop dispatch boundary for the claims above. Perf baseline and scheduler execution remain NOT VERIFIED.

äļ­æ–‡åیæ•īį‰ˆ

åœĻ 1325b8386c äļŠįš„æœŽåœ°éЌčŊ - åœĻ agent-loop æīū发čūđį•ŒåˆĪ PASS

čŋ™æ˜ŊåŊđäđ‹å‰äļĪäŧ―æŠĨå‘Šįš„čĄĨ充。é‚ĢäļĪäŧ―éƒ―įŧ‘åœĻå·ēčĒŦ取äŧĢįš„ head fcd06e8988 äļŠïžŒåŽŸæ–‡äļ€å­—朊åŠĻ。朎č―ŪčĶ†į›–å―“å‰ headïžšåۃåœĻæ­Īå‰åŸšįĄ€äļŠåĪšäš† 44c8d57936、82384bd1b5 äļĪäļŠæäšĪåĪ–åŠ äļ€æŽĄ origin/main 合åđķ。

  • æĻĄåžïžšmerge-referenceäļ‰č‡‚
  • before č‡‚ïžšåˆåđķ埚įšŋ c61044fae46cfeed70b882140730c0caaa944524
  • after č‡‚ïžšlive head 1325b8386c5c1dfeaf0fab4d822134a65336da38
  • įŽŽäļ‰č‡‚ïžšfcd06e89880427e21b87547a3a0f8234759e441eåģäļŠäļ€äŧ―æŠĨ告åˆĪ PASS įš„é‚ĢäļŠ head。加åۃæ˜Ŋäļšäš†æŠŠäļĪäļŠæ–°æäšĪįš„æ•ˆæžœé‡å‡šæĨïžŒč€Œäļæ˜Ŋ假čŪūåۃäŧŽæœ‰æ•ˆã€‚
  • 观æĩ‹éĒ协čŪŪ / agent-loop æīū发。åˆĪæŪoracleæ˜ŊᜟåŪž Turn.run å‘å‡šįš„ LlmEventType.ToolCallRequestå…ķčū“å…ĨæĨč‡ŠįœŸåŪž LlmChat.sendMessageStream åœĻ llm-chat.ts įš„ XML fallback 调į”Ļį‚đ。é™Ī provider æ˜Ŋč„šæœŽåŒ–įš„äŧĨåĪ–ïžŒé“ūč·ŊäļŠæēĄæœ‰ mock。
  • 发åļƒå‰é‡čŊŧ CIïžšåŊđ commits/<sha>/check-runs å…Ļ量分éĄĩ、按名字取最新38 äļŠäļåŒæĢ€æŸĨ名 -> 12 success / 26 skipped / 0 failure / 0 cancelled / 0 朊åŪŒæˆïž›required context Lint & Static (ubuntu-latest, Node 22.x) completed success。

head 变åŠĻæ˜Ŋ有åŪžčīĻ内åŪđįš„

äļŠäļ€äŧ― PASS åŊđåۃčĶ†į›–įš„čŒƒå›īæ˜ŊåŊđįš„ïžŒä―† fcd06e8988 äļŠčššį€äļĪäļŠåŪƒįš„č§čŊé›†æēĄæœ‰č§ĶåŠįš„æīŧįžšé™·ã€‚äļĪäļŠéƒ―åœĻå―“å‰ head å…ģ闭䚆å…ķäļ­äļ€äļŠæ–đ向æ˜ŊäļåŪ‰å…Ļįš„ã€‚

见čŊ before c61044fae4 fcd06e8988äļŠäļ€äŧ―æŠĨ告 after 1325b8386c
GATEčĢ…į―Ūæœ‰æ•ˆæ€§ïž‰ 1 [read_file] 1 [read_file] 1 [read_file]
W1 - #13492 äļŧ见čŊ 1 [read_file]åĪ–åą‚ write_file äļĒåĪą 2 [write_file,read_file] 2 [write_file,read_file]
W6 - čĒŦæģĻé‡ŠæŽ‰įš„ rm -rf /tmp/x 2run_shell_command čĒŦæīū发 1 [read_file] 1 [read_file]
W10 - æœŠå—å―ąå“č·Ŋåū„åŊđį…§ 2 [read_file,write_file] 2į›ļ同 2į›ļ同
W11 - 后文æļļįĶŧįš„ example įŧ“束标čŪ° 1 [read_file] 0 []æīŧ调į”ĻčĒŦ吞掉 1 [read_file]
W12 - åž•å·åŽč·ŸčĢļ尖拎号 1 [rm]åĩŒåĨ— rm -rf čĒŦæīū发 1 [rm]äŧį„ķčĒŦæīū发 0 []

äļĪįąŧå―’å› åŋ…éĄŧ分垀čŊī因äļšåۃäŧŽäļæ˜Ŋ同äļ€į§äŋŪåĪïžš

  • W11 æ˜Ŋ朎 PR č‡Šå·ąåž•å…Ĩã€åˆč‡Šå·ąæŠ“å‡šæĨįš„å›žå―’ã€‚ 合åđķ埚įšŋčŋ”回 [read_file]ïžŒč€Œ fcd06e8988 čŋ”回 []。44c8d57936 把歧äđ‰åŒšįš„æ‰Đåž čŒƒå›īæ”ķįī§åˆ°ã€ŒčŊĨ倞åŪžé™…æœŠé—­åˆįš„ opener 数量」äļå†čŪĐå…Ļč―Ūäŧŧæ„ä―į―Ūįš„æœ€åŽäļ€äļŠ example įŧ“束标čŪ°æĨįŧˆįŧ“æŽĐį ïžŒäŧŽč€ŒæĒåĪäš†åŸšįšŋ行äļšã€‚
  • W12 æ˜Ŋ合åđķ埚įšŋäļŠå°ąå·ē存åœĻįš„įžšé™·ïžŒæœŽ PR įŽ°åœĻ把åۃå…ģ掉䚆。 埚įšŋäļŽ fcd06e8988 éƒ―äžšäŧŽäļ€äļŠã€Œæ‰Ŧ描å™Ļ刚刚åˆĪåۚå…ķčūđį•ŒäļåŊäŋĄã€įš„ block 里æīū发åĩŒåĨ—įš„ rm -rf /tmp/x。82384bd1b5 æ”đäļšäŧŽæ‰åđģåŒđ配æœŦå°ūįŧ­æ‰ŦïžŒč€Œäļæ˜ŊäŧŽé‚ĢäļŠåˆščĒŦč‡Šå·ąåĶåŪšįš„åž•å·é…åŊđæļļčĩ°æ‰€äš§į”Ÿįš„偏į§ŧįŧ­æ‰Ŧ。

W1 åœĻ head äļŠįš„åŪžå‚å­—čŠ‚åĶ‚äļ‹ïžščĒŦåž•į”Ļįš„č°ƒį”Ļä―œäļšæ•°æŪį•™åœĻ content 里äŋæŒæƒ°æ€§ïžŒåŒįš§įš„čŊŧ调į”Ļį…§åļļæīū发。

凚åĢæ”ķ敛containment

before / after äļĪč‡‚åŠå·Ūäļ€äļŠæ–‡äŧķ所äŧĨäļŠéĒæŊäļ€åĪ„åˆ†æ­§éƒ―åŊå―’å› äšŽæœŽ PRïžš

äš§į‰Đ before prev after
xml-tool-call-fallback.ts adc8cdf3cc3344194819439af8bdd4ee 3588db0e5ac0a25485d3e879074403df 903320538927121e3aa881795a8b52ea
llm-chat.test.tséĐąåŠĻåŪŋäļŧ 6d469a4ea443cea2e21b7b59459307dc dab7c70bbf9375107af39ccabb9cee78 6d469a4ea443cea2e21b7b59459307dc
turn.ts 3dbb27ec21fb81020341b962e4b944ad 同 同
llm-chat.ts c26105e8a0de1134a606ccdb4bf0620b 0887da58bd4f76397ea78582f38d1a19 c26105e8a0de1134a606ccdb4bf0620b
éĐąåŠĻæ•ī文äŧķ 7305957c1139f5e4cb2c8e0fc77c652e 6ab77e2a7edb7606a10023f268cdd985 7305957c1139f5e4cb2c8e0fc77c652e
äŧ…éĐąåŠĻ插å…Ĩ块 0044acd22dad354e89973095323f3878 0044acd22dad354e89973095323f3878 0044acd22dad354e89973095323f3878

插å…Ĩįš„éĐąåŠĻ块åœĻäļ‰č‡‚é€å­—čŠ‚į›ļ同。prev č‡‚įš„åŪŋäļŧ文äŧķäļåŒïžˆåˆįš„æ˜Ŋæ›īæ—Đįš„ main所äŧĨåŪƒįš„æ•ī文äŧķéĐąåŠĻ md5 äđŸäļåŒ —— čŊĨč‡‚åŠæ˜Ŋčū…åŠĐč‡‚ïžŒį”Ļ途äŧ…æ˜Ŋ隔įĶŧé‚ĢäļĪäļŠæ–°æäšĪ。

pnpm-lock.yaml åœĻ before、after äļŽ node_modules 捐čĩ č€…äļ‰åĪ„åŒäļš bd920144edd322ad19245bc0ccfb884bïž›äūčĩ–į”ĻįĄŽé“ūæŽĨåĪåˆķ䚋后åĪæ ļ捐čĩ č€…äŧäļš 1204/14 朊受损。需č́æŠŦéœēįš„äļ€į‚đïžšprev č‡‚įš„ lockfile æ˜Ŋ 2f6948528bc40b0ccf3c12956ec16137因æ­ĪåŪƒįĄŽé“ūæĨįš„äūčĩ–äļŽåŪƒč‡Šå·ąįš„ lock åđķäļäļĨæ žåŊđ嚔。prev č‡‚įš„äļĪæĄįŧ“čŪšéƒ―į”ąæīū发éĐąåŠĻį‹ŽįŦ‹ä―čŊïžŒäļ”äļ‰č‡‚č·‘įš„æ˜Ŋ同äļ€äŧ―äūčĩ–。

æŊäļŠč‡‚įš„ git status --porcelain åŠå‡šįŽ°äļ‰äļŠ scratch 文äŧķpatrol-driver.test.ts、xml-pin.test.ts、verify.vitest.config.tsæēĄæœ‰įǰäŧŧä―•äš§å“æ–‡äŧķ。

å›žå―’é’‰

把 PR č‡Šå·ąæäšĪįš„ xml-tool-call-fallback.test.ts é€å­—čŠ‚åĪåˆķčŋ›äļ‰č‡‚6403ccca6f25f839b08281b021bce86a原样čŋčĄŒïžš

臂 įŧ“æžœ æ”ķ集数
before c61044fae4 34 failed / 117 passedrc=1 151
fcd06e8988 2 failed / 149 passedrc=1 151
after 1325b8386c 151 passed / 0 failedrc=0 151

äļ‰č‡‚æ”ķé›†æ•°éƒ―æ˜Ŋ 151所äŧĨčŋ™äš›æ˜Ŋ断čĻ€įš§åĪąčīĨäļæ˜Ŋæ”ķ集é˜ķæŪĩåīĐæŽ‰ã€‚fcd06e8988 äļŠåĪąčīĨįš„é‚Ģ 2 æĄæ­Ģæ˜ŊäļĪäļŠæ–°æĩ‹čŊ•ïžšdoes not let a stray later closer widen a balanced ambiguous region äļŽ does not rescan a rejected block when a bare bracket follows the quote。

fixture įš„é€å­—čŠ‚äļ€č‡īæ˜ŊčŊå‡šæĨįš„ïžŒäļæ˜Ŋ假åŪšįš„ïžšå·ē提äšĪįš„ fixture č‡Šå·ąæ‰“å°æ‘˜čĶïžŒäļ‰č‡‚į›ļ同äļ”äļŽæīū发éĐąåŠĻį”Ļ标į­ūåļļé‡į‹ŽįŦ‹é‡åŧšå‡šæĨįš„č§čŊį›ļ同 ——

  • W11 len=288 md5=35f85b9736e3431659b808380d83a026
  • W12 len=125 md5=29e5c58eeab6d36f6eadf3985ecb9079

åŊđį…§

  • GATE åœĻäļ‰č‡‚éƒ―čƒ―æīū发äļ€äļŠåđģå‡Ąįš„åˆč§„ read_file所äŧĨäļŠéĒæŊäļ€äļŠ 0 éƒ―æ˜Ŋ「čŊ·æą‚é“ūč·Ŋæ­Ģåļļä―†įĄŪåŪžæēĄæīū发」äļæ˜ŊčĢ…į―ŪåĪąæ•ˆæˆ–æå‰é€€å‡šã€‚
  • W10æ™Ūé€šåˆč§„č°ƒį”Ļ无垕į”Ļ、无文æĄĢ匚åœĻäļ‰č‡‚éƒ―æ˜Ŋ 2 [read_file,write_file]ïžŒæœŠå—å―ąå“č·Ŋåū„æēĄčĒŦåŠĻčŋ‡ã€‚

æ€§čƒ―åĢ°æ˜ŽïžšæœŠčƒ―åĪįŽ°

äļŠäļ€äŧ―æŠĨ告把 PR 臩čŋ°įš„æ€§čƒ―埚įšŋčŪ°äļš NOT VERIFIED因äļšåۃæ˜Ŋæ‹ŋ PR č‡Šå·ąåˆ†æ”Ŋįš„æ›īæ—ĐäŋŪčŪĒį‰ˆå―“åŸšįšŋé‡įš„ã€‚æˆ‘æ”đį”Ļ合åđķ埚įšŋåđķį”Ļæˆ‘č‡Šå·ąæž„é€ įš„čīŸč――æĨ量æŽĒ针åœĻäļ‰č‡‚é€å­—čŠ‚į›ļ同9f87dadc058af24055d8ea1079b974e3取 5 æŽĄäļ­ä―æ•°ïž‰ïžš

čīŸč―― before fcd06e8988 after
P1 å›ī栏内 1000 äļŠį•ļå―Ē opener45008 B 12.09 ms 12.80 ms 12.91 ms
P2 64 KB example æ­Ģ文65558 B 0.21 ms 0.30 ms 0.32 ms
P3 64 äļŠåž•į”Ļ型 block8448 B 0.50 ms 2.77 ms 2.83 ms
P4 256 äļŠåž•į”Ļ型 block33792 B 2.76 ms 13.90 ms 13.93 ms

æˆ‘įš„é‡æž„æēĄčƒ―åĪįŽ° PR 臩čŋ°įš„ ~2.2 s -> ~1 ms äļŽ ~420 ms -> ~13 ms。čŋ™äļį­‰äšŽčŊī PR įš„æ•°å­—æ˜Ŋé”™įš„ —— æˆ‘įš„ fixture äļæ˜Ŋäš§į”Ÿé‚Ģäš›æ•°å­—įš„å―ĒįŠķ我äđŸæēĄæœ‰čŋ˜åŽŸå‡šé‚Ģ乛å―ĒįŠķã€‚æˆ‘čƒ―įĄŪčŪĪįš„æ˜Ŋ PR č‡Šå·ąå·ēåœĻ #13787 æŠŦéœēįš„é‚ĢéƒĻ分æŪ‹į•™ïžšåž•į”Ļ型č·Ŋåū„įš„åž€é”€įšĶäļšåˆåđķ埚įšŋįš„ 5 倍P3 0.50 -> 2.83 msP4 2.76 -> 13.93 ms。åœĻčŋ™äļŠé‡įš§äļŠïžŒblock 数 64 -> 2564 倍åŊđåš”č€—æ—ķįšĶ 4.9 倍所äŧĨæŠŦéœēįš„ã€Œé€å€žå›ī栏æ‰Ŧ描」įĄŪåŪžå­˜åœĻïžŒä―†åœĻæ­Ī匚é—ī内我æēĄæœ‰č§‚åŊŸåˆ°åđēå‡€įš„äšŒæŽĄåĒžé•ŋ。

čūđį•Œ - 朎č―ŪæēĄæœ‰č§ĶåŠįš„éƒĻ分

  • 调åšĶå™ĻåŊđå·Ĩå…·įš„åŪžé™…æ‰§čĄŒã€‚åˆĪæŪ停åœĻ ToolCallRequest 发凚äđŸå°ąæ˜Ŋ调åšĶå™Ļæķˆčīđįš„é‚Ģäļ€åą‚å…ķåŽįš„æ‰§čĄŒæ­ĨéŠĪæēĄæœ‰č§‚æĩ‹ã€‚čŪ°äļš NOT VERIFIEDäļæ˜Ŋ N/A。
  • æēĄæœ‰č·‘åیæ•īįš„ CLI / TUI / Web UI。é‚ĢæĄč·Ŋįšŋ需č́ workspace dist é“ūįšĶ 13 æŽĄæž„åŧšã€å•臂įšĶ 10 分钟朎č―ŪéĒ„įŪ—æ”ūäļäļ‹ã€‚
  • provider æ˜Ŋč„šæœŽåŒ–įš„ïžŒå› æ­Īčŋ™æ˜Ŋã€ŒįœŸåŪžæ ˆ + 受控 providerã€įš§čŊæŪäļæ˜ŊᜟåŪžæĻĄåž‹éЌæ”ķïž›åŊđæīŧæĻĄåž‹äžščū“凚äŧ€äđˆäļåšäŧŧä―•æ–­čĻ€ã€‚
  • Windows äļŽ macOS 朊åœĻ朎朚čŋčĄŒã€‚
  • 12 æĄæœŠ resolve įš„čŊ„åŪĄįšŋįĻ‹äļŽ reviewDecision=CHANGES_REQUESTED åąžčŊ„åŪĄč́å†ģ。朎地 PASS äļč§Ģå†ģåۃäŧŽïžŒäđŸäļæž„成合åđķæ‰đ准。

åĪįŽ°å‘―äŧĪ

见ä–đč‹ąæ–‡æŪĩ Repro。čŊæŪ、æ—Ĩåŋ—äļŽäŋį•™įš„čĢ…į―Ūä―äšŽ /data/pr-verify/evidence/13579/1325b8386c5c1dfeaf0fab4d822134a65336da38/bqtbsuwh0407/

įŧ“čŪšïžšäŧĨäļŠåĢ°æ˜ŽåœĻ agent-loop æīū发čūđį•ŒåˆĪ PASSïž›æ€§čƒ―åŸšįšŋäļŽč°ƒåšĶå™Ļæ‰§čĄŒäŧäļš NOT VERIFIED。

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Parking this as a draft until the recoverer is rewritten — it is not landable in its current shape, and leaving it in the review queue keeps burning review rounds on the same clusters.

State at 1325b838: 12 unresolved Criticals, of which six are fix-induced regressions in this diff's own recovery path (adjacent real calls swallowed, and an unbounded re-scan that goes quadratic on quoted blocks). Two more say the new fixtures pin a contract nobody agreed to — they assert that a destructive call quoted inside a parameter gets dispatched. The review's own conclusion is that the loop will not close by fixing instances one at a time:

A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster.

The branch is untouched and still has everything; the useful next step is a fresh PR that rebuilds recoverOuterXmlCalls around a single structured pass (documented ranges instead of the flat regex), with the fixture contract re-derived from what the parser should accept rather than from the current behaviour.

Reopening this draft is a one-click undo if that plan is wrong.

@yiliang114
yiliang114 marked this pull request as draft October 23, 2026 16:39
Recovery re-derived "is this documentation?" inside every guard: fence
state per query, the flat parameter mask per scan, the comment/indented
ranges in one pass and the example ranges in two. Each fix for a leak in
one view opened another in a different view (19 review rounds, 13
standing Criticals), because the views disagreed about which text is
data.

Collapse them into one derivation per turn (deriveTurnAnatomy): element
pairing by depth, then the spans parameter data owns, then fence state,
then comments and indented code, then the lexer's view of the prose,
then the example wrappers. Every guard reads that model; none of them
rescans the text or re-derives a mask.

Consequences:

- The flat PARAMETER_PATTERN no longer decides what is data. Depth
  pairing gives an element the extent it owns and leaves a prose mention
  unclosed, so a mention can no longer swallow an example wrapper or a
  comment mask.
- A value the stream never closed suppresses comment terminators and the
  unterminated-comment veto, but it does not hide the documentation
  markers that follow it: a marker after a truncated stream must keep
  protecting the rest of the turn.
- Fence state is one per-line table, so recovery is linear in turn
  length. On the dense-comment payload: 18/72/144KB 5.5/83.1/405.9ms ->
  0.4/0.9/0.8ms; on the ambiguous-region shape 25.8/1081.8/8287.5ms ->
  2.5/2.2/5.2ms.
- The refreshed second example pass and the mutable parameterRanges mask
  are gone, so no consumer can read a stale view.

Two fixtures pinned the opposite contract and are corrected. R2-1
dispatched a destructive shell the model had written as write_file
content while the outer write was lost; it now expects the decline
polarity the merge base also took. R7-1 dispatched a call that sat
inside an example wrapper; it now expects only the wrapper's own block.
Neither expectation was weakened to make the change pass: both were red
under the previous parser for the mechanism they now pin.

Closes the standing mechanisms R10-1 R11-1 R12-1 R12-2 R12-3 R13-1
R13-2 R13-4 R14-1 R15-1 R15-2 R15-3 R15-4. Refs #13492.
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Root-cause rework: one derivation per turn

The standing Criticals were not independent bugs. Recovery derived "is this text documentation?" in four different places — fence state per query, the flat parameter mask per scan, comments/indented code in one pass, example wrappers in two — and each derived a slightly different answer. Every fix for a leak in one view opened a leak in another, which is why 19 rounds kept producing siblings.

d9e4e1b replaces them with a single deriveTurnAnatomy(text) computed once per turn, in dependency order, with one documented precedence:

  1. element pairing by depth (calls and parameters in one ordered walk) decides which spans parameter data owns;
  2. a value the stream closed is data for every guard;
  3. a value the stream never closed suppresses a comment terminator and the unterminated-comment veto, but does not hide the markers themselves — otherwise a marker after a truncated stream would silently stop protecting the rest of the turn;
  4. fences are a per-line state table over non-data lines;
  5. comments / indented code are derived over non-data, non-fence lines;
  6. example wrappers are derived over non-data, non-fence, non-documentation lines, with the per-value ambiguity debt accumulated in the same walk.

Every guard (block skip, both-end eligibility, the prose measure) reads that object. No consumer re-derives a mask or rescans the text.

Per-finding closure

id mechanism (as filed) how the model closes it test that reddens on 1325b83
R10-1 hiddenDocumentationRanges re-derives CommonMark block structure independently one line index + one fence table + one documentation pass; the second block-structure derivation is deleted structural — no dedicated case (see residuals)
R11-1 pass 1 and the refresh read two views of parameterRanges; hiddenDocs was a stale snapshot only one derivation exists; the extendedMasks refresh branch is deleted and lexInline runs exactly once keeps the single lexer pass length-preserving over value data (old: called 2×)
R12-1 the containment filter's precondition was satisfiable by prose ownership now requires the truncated element to be a clean direct child of an open call body — prose between the open tag and the element disqualifies it does not let a prose call opener unmask a comment
R12-2 seeded-0 underflow + loop 2's divergent skip set let end run to the last closer one skip set (all parameter data) and count-based termination (count -= 1; if (count <= 0) close); a seeded-zero underflow cannot occur structural + the three existing ambiguity fixtures (:1926, :1942, :1162) stay green; no dedicated case (see residuals)
R12-3 fixture :2024 certified dispatch of a call inside a prose <example> wrapper the prose parameter mention stays inert under depth pairing, so the wrapper's closer is structure and the call is documentation fixture flips to ['write_file']
R13-1 the flat lazy parameter scan paired a prose mention with the next closer, blinding both documentation guards depth pairing leaves the mention unclosed and therefore owning nothing does not let a prose parameter mention release an example-wrapped call
R13-2 positionInsideFence was O(n) per comment opener, unbounded on the comment path fence state is one table; terminators are collected once; both queries are lookups scans the turn a constant number of times regardless of comment openers (old: 199 prefix splits, new â‰Ī 3)
R13-4 the hidden-documentation mask was never passed to the example-tag path the example scan skips tag positions inside documentationSpans, derived from the same model does not let an indented example closer end a documentation region
R14-1 fixture :1820 certified dispatch of the shell the model wrote as write_file content the unclosed content value is data; its quoted call is not a block; the truncated block declines fixture flips to []; sibling case recovers the sibling after an unclosed value
R15-1 unclosedCallOpenerOffsets popped shape-only, and the exemption could be reached three ways the function is deleted; closers pair only with their own dialect; ownership is decided by the clean-direct-child rule, not by opener bookkeeping ownership half: does not let a prose call opener unmask a comment; dialect half structural
R15-2 one fresh full-text scan per ambiguous region, nested inside the O(n) fence rescan the debt is accumulated during the single example-tag walk; no per-region rescan exists same scan-count case + measurement below
R15-3 the indented-code arm had no value-ownership term indented code cannot start inside parameter data keeps an indented line inside a value from becoming documentation (old: the write was lost)
R15-4 the terminator skip consulted closed values only the terminator list excludes --> inside closed and never-closed values does not end a comment at a terminator inside a never-closed value

One invariant I added while closing the above, because the first draft of the model broke it: a truncated value must not hide the documentation markers after it (keeps documentation markers working after a truncated value, old head is red on the fence/example arms).

The two fixtures, and why the expectations moved

  • xml-tool-call-fallback.test.ts:1820 (R2-1) asserted ['run_shell_command']: the model had written the shell as write_file content, the outer write was lost, and the quoted command reached the scheduler. It now asserts [] — the decline polarity the merge base also took for this input. Nothing was weakened; the write is genuinely unrecoverable with an unterminated content value.
  • xml-tool-call-fallback.test.ts:2024 (R7-1) asserted ['write_file', 'run_shell_command']: the second call sat between a prose <example> opener and its closer, i.e. inside documentation. It now asserts ['write_file'].

Both were red under the previous parser for exactly the mechanism they now pin. A third expectation changed as a consequence, not as a contract shift: keeps the single lexer pass length-preserving over value data (the refreshed second pass no longer exists).

Verification (real runs, this worktree at d9e4e1b)

focused:  cd packages/core && npx vitest run src/core/xml-tool-call-fallback.test.ts
          158 passed (158)   [was 151; +7 cases, 2 fixtures flipped, 1 count assertion]
affected: npx vitest run src/core/llm-chat.test.ts
          609 passed (609)
types:    npx tsc --noEmit                                  -> 0
lint:     npx eslint <both files>                           -> 0
format:   npx prettier --check <both files>                 -> clean

Red-on-revert: with the parser restored to 1325b83 and this test file kept, 11 tests fail — the 7 new cases, the 2 flipped fixtures, the sibling case and the lexer-pass count — and all 11 pass again at d9e4e1b. Fuzzing the two parsers against each other over 4000 fragment-generated turns: 0 differences; on a curated 24-shape corpus the only 6 differences are turns where the old parser dispatched a call the model classifies as documentation, all in the fail-closed direction.

Cost, same machine, best of 3 (tryRecoverXmlToolCalls through the production gate):

payload 18 KB 72 KB 144 KB
dense comments, 1325b83 5.5 ms 83.1 ms 405.9 ms
dense comments, d9e4e1b 0.4 ms 0.9 ms 0.8 ms
ambiguous regions, 1325b83 25.8 ms 1081.8 ms 8287.5 ms
ambiguous regions, d9e4e1b 2.5 ms 2.2 ms 5.2 ms

The shipped case pins the shape (prefix-split count), not the wall clock.

Residuals — stated, not hand-waved

  1. R12-2 and R10-1 have no dedicated new red test. Their mechanisms are deletions (the divergent second scan, the duplicate block-structure derivation); I could not reconstruct the R12-2 witness shape without re-introducing the divergence it depends on. The three fixtures that pinned the surrounding ambiguity contract (:1162, :1926, :1942) remain green, and both closures are structural.
  2. This is not literally one character walk. marked supplies the <example> token positions, so the model is one derivation object with one pass per concern in dependency order; the property that matters — every consumer reads the same object and nothing rescans — holds.
  3. A complete call inside a never-closed value still recovers (pinned by the existing recovers the intact call after an envelope whose block never closed). I did not change that contract; only blocks inside a value whose owning call closed are treated as quoted markup.
  4. An unterminated comment whose opener sits inside an owned truncated value still does not mask — that is the pinned XML tool-call recovery drops outer calls containing quoted tool markup #13492 truncated-stream behavior. The fail-closed repair is that markers after the value keep working.
  5. The production diff grows by ~200 lines (883 → 1078) even though two derivations were deleted: the model carries the span types and the precedence documentation that the old code spread across ad-hoc guards. The PR remains over the 1500-addition advisory ceiling; this is a root-cause replacement, not a narrow revert.
  6. The timing table is single-machine; treat it as a shape signal, not a bound.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Branch refresh: origin/main merged (no code change)

Head is now c4dd2f0676acc9438ef29956ec49eaac7164586c — a single ordinary merge commit on top of d9e4e1b. No rebase, no force, no history rewrite; the previous head remains an ancestor.

  • Merge base moved c61044fa → 8d4e2d4f3222 (247 commits of main). This is what cleared the two staleness reds: Lint & Static failed at Check lint gate freshness (main changed ci.yml, scripts/lint.js, eslint.legacy-core-barrel-imports.mjs and the gate script on 10-24â€Ķ10-27) and Test (ubuntu) exited 127 because the job checks out the head SHA while .github/scripts/ci/run-unit-test-lanes.sh only existed on main.
  • Conflicts: none. git rev-list --count c61044fa..8d4e2d4f -- <parser> <test> is 0, so main never touched the two files this PR changes; the merged tree's parser and test are byte-identical to d9e4e1b (git diff d9e4e1b HEAD -- <both files> is empty), and every other path took main's side by construction of the merge. Nothing needed re-applying, and there was no upstream parser fix to reconcile.
  • Re-verified on the merged tree (not carried over): focused parser tests 158/158, llm-chat 661/661 (609 before the merge — main added 52), packages/core tsc --noEmit 0, ESLint 0, Prettier clean. Red-on-revert re-run against the pre-rework parser blob (1325b83): the same 11 tests fail, and all 158 pass again after restoring the verified file (sha256 checked).
  • CI snapshot at c4dd2f0: Lint & Static pass (including Check lint gate freshness, ESLint, actionlint, yamllint, sensitive-keyword), Test (ubuntu-latest) pass, Managed Agent gate pass; 12 pass / 12 skipping / 3 pending (web-shell E2E smoke, TUI parity, Node 26 acceptance) at snapshot time, none failed.
  • Evidence binding: the per-finding closure table in the previous comment stays bound to d9e4e1b; this merge changes no parser line, so those mechanisms and their tests are unchanged — the counts above are the same suite re-run on the refreshed base.

Still draft. No review threads resolved, no reviews dismissed, no other branch touched.

@yiliang114
yiliang114 marked this pull request as ready for review October 27, 2026 10:00

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head c4dd2f06 (+1984/−203, 2 files). The number that matters here is 12 unresolved threads, all [Critical], so I checked each against this head rather than trusting the count: 10 are isOutdated anchors, and the two whose anchors still land on current lines have been changed to the polarity the bot asked for.

The two live-anchored Criticals

  • R14-1 (xml-tool-call-fallback.test.ts:1827) said the fixture pinned expected: ['run_shell_command'] for a turn where a write_file content value quotes a destructive shell call — i.e. it certified the dispatch of the command the model wrote as file content. At this head the same case is expected: [], with the comment stating that declining is the in-contract polarity (base declined it too) and that the old expectation dispatched the destructive command. The fixture now asserts the polarity the finding demanded.
  • R12-3 (:2034) said the fixture pinned ['write_file', 'run_shell_command'], certifying execution of a call sitting between a prose <example> opener and its closer. At this head it is expected: ['write_file'], with the comment recording that the old expectation certified the dispatch the flat scan's mis-pairing caused.

Both are still open as threads because nobody resolved them, not because the code disagrees with them.

The other 10 are anchored to lines that no longer exist (isOutdated), spanning R10-1 → R15-4. Per the repo's own convention that is what a re-derived finding looks like after the code has moved; it is not evidence that they are still live.

Where this leaves the PR

  • The standing CHANGES_REQUESTED is qwen-code-review-bot at 1325b838 — an older head than c4dd2f06 — so the verdict is stale by head, and the ledger's own floor is critical with 12 posted findings across rounds 10–15.
  • CI at this head is green on Test, Lint & Static, Integration Tests (no-AK) and Managed Agent gate; TUI parity and review-pr were still running.
  • What is genuinely left is not a code question I can settle from the diff: whether this PR should recover per block (the bot's reading) or decline the whole turn (the author's, and base's). That is the maintainer decision already recorded in #13492.

I am not resolving those twelve threads or dismissing the review: resolving them would assert the findings are closed, and dismissing would assert the review is stale, and neither is mine to decide on a PR where the disagreement is about the intended contract.

äļ­æ–‡čŊŽ

åŪĄé˜…äš† head c4dd2f06+1984/−2032 äļŠæ–‡äŧķ。čŋ™é‡Œč́įī§įš„æ•°å­—æ˜Ŋ 12 æĄæœŠč§Ģå†ģįšŋįĻ‹ã€å…Ļæ˜Ŋ [Critical]所äŧĨ我æēĄæœ‰åŠäŋĄčŋ™äļŠčŪĄæ•°ïžŒč€Œæ˜Ŋé€æĄåŊđį€å―“å‰ head æ ļčŋ‡ïžšå…ķäļ­ 10 æĄįš„é”šį‚đå·ē isOutdatedïžŒč€Œé”šį‚đäŧč―åœĻå―“å‰čĄŒäļŠįš„é‚ĢäļĪæĄïžŒå·ēįŧčĒŦæ”đ成 bot čĶæą‚įš„é‚ĢäļŠæžæ€§ã€‚

äļĪæĄé”šį‚đäŧæœ‰æ•ˆįš„ Critical

  • R14-1xml-tool-call-fallback.test.ts:1827čŊīįš„æ˜ŊïžščŊĨ fixture 把 expected: ['run_shell_command'] å―“æˆåĨ‘įšĶé’‰ä―ïžŒč€Œé‚Ģäļ€č―Ū里 write_file įš„ content 倞垕į”Ļ乆äĄį ī坏性 shell 调į”Ļâ€”â€”į­‰äšŽčŪĪčŊäš†ã€ŒæĻĄåž‹å†™åœĻ文äŧķ内åŪđé‡Œįš„å‘―äŧĪčĒŦæīūå‘ã€ã€‚å―“å‰ head 同äļ€į”Ļäū‹æ˜Ŋ expected: []æģĻ释写明「拒įŧæ‰æ˜ŊįŽĶ合åĨ‘įšĶįš„æžæ€§ïžˆbase äđŸæ‹’įŧïž‰ã€ïžŒåđķæŒ‡å‡šæ—§įš„æœŸæœ›äžšæīū发é‚ĢæĄį īåæ€§å‘―äŧĪ。fixture įŽ°åœĻ断čĻ€įš„æ­Ģæ˜ŊčŊĨ finding čĶæą‚įš„æžæ€§ã€‚
  • R12-3:2034čŊīįš„æ˜Ŋïžšfixture é’‰įš„æ˜Ŋ ['write_file', 'run_shell_command']čŪĪčŊäš†äļ€äļŠåĪđåœĻ prose <example> 垀标į­ūäļŽå…ķ闭标į­ūäđ‹é—īįš„č°ƒį”ĻčĒŦæ‰§čĄŒã€‚å―“å‰ head æ˜Ŋ expected: ['write_file']æģĻ释čŪ°ä—§įš„æœŸæœ›čŪĪčŊäš†æ‰åđ쿉Ŧæé”™é…é€ æˆįš„æīū发。

čŋ™äļĪæĄč‡ģäŧŠäŧåž€į€ïžŒæ˜Ŋ因äļšæēĄäššåŽŧ resolve åۃäŧŽïžŒč€Œäļæ˜ŊäŧĢ᠁čŋ˜åœĻ反éĐģåۃäŧŽã€‚

åĶåĪ– 10 æĄé”šåœĻå·ēįŧäļå­˜åœĻįš„čĄŒäļŠïžˆisOutdatedč͆ᛖ R10-1 → R15-4。按äŧ“åš“č‡Šå·ąįš„åĢåū„čŋ™å°ąæ˜ŊäŧĢ᠁į§ŧåŠĻäđ‹åŽã€Œé‡æ–°æŽĻåŊžå‡šæĨįš„ findingã€įš„æ ·å­ïžŒäļčƒ―å―“ä―œåۃäŧŽäŧ live įš„čŊæŪ。

čŋ™åž  PR įŽ°åœĻįš„åĪ„åǃ

  • äŧæŒ‚į€įš„ CHANGES_REQUESTED æ˜Ŋ qwen-code-review-bot åœĻ 1325b838 äļŠįš„——æŊ” c4dd2f06 旧——所äŧĨé‚Ģåž įĨĻæŒ‰ head å·ēįŧčŋ‡æœŸïž›åŪƒįš„ ledger floor æ˜Ŋ criticalïžŒįŽŽ 10–15 č―Ūå…ą 12 æĄã€‚
  • čŊĨ head įš„ CIïžšTest、Lint & Static、Integration Tests (no-AK)、Managed Agent gate įŧŋïž›TUI parity äļŽ review-pr čŋ˜åœĻč·‘ã€‚
  • ᜟæ­Ģå‰Đäļ‹įš„äļæ˜Ŋæˆ‘čƒ―äŧŽčŋ™äļŠ diff 里åۚčŪšįš„äŧĢ᠁é—ŪéĒ˜ïžščŋ™åž  PR åš”čŊĨ「逐 block æĒåĪã€ïžˆbot įš„čŊŧæģ•čŋ˜æ˜Ŋ「æ•īč―Ūæ‹’įŧã€ïžˆä―œč€…äļŽ base įš„čŊŧæģ•。é‚Ģæ˜Ŋ #13492 里å·ēįŧčŪ°å―•äĨįš„įŧĪ者č́å†ģ。

我äļäžšåŽŧ resolve é‚Ģ 12 æĄįšŋįĻ‹ïžŒäđŸäļäžš dismiss é‚Ģåž  reviewïžšresolve į­‰äšŽåŪĢį§° finding å·ē闭dismiss į­‰äšŽåŪĢį§° review čŋ‡æœŸïžŒč€ŒåœĻäļ€äļŠåˆ†æ­§æœŽčšŦå°ąæ˜Ŋ「į›Ū标åĨ‘įšĶčŊĨæ˜Ŋ哊äļŠã€įš„ PR äļŠïžŒčŋ™äļĪäŧķäš‹éƒ―äļčŊĨį”ąæˆ‘æĨåŪšã€‚

@yiliang114 yiliang114 added scope/core type/bug Something isn't working as expected labels Oct 27, 2026

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary — PR #13579

čŊ„åŪĄįŧ“čŪšïžšé€ščŋ‡ïžˆApprove

朎 PR 重构 XML tool call 提取é€ŧčū‘äŋŪåĪ #13492 äļ­å‚数倞垕į”ĻåĩŒåĨ—č°ƒį”Ļæ—ķčĒŦ错čŊŊ dispatched įš„äļĨ重é—ŪéĒ˜ã€‚čŋ™æ˜Ŋäļ€äļŠ Deep tier įš„ parser įš§å˜æ›īæķ‰åŠæŒäđ…åŒ–æ žåžįš„čūđį•ŒåˆĪåŪšã€‚


æ ļåŋƒå˜æ›īčŊ„äŧ·

1. deriveTurnAnatomy() — å•æŽĄæīūį”Ÿæžķ构

  • 将 fence įŠķ态、documentation spans、value spans、example spans įŧŸäļ€åœĻäļ€æŽĄ ordered walk äļ­æīūį”ŸïžŒæķˆé™Ī䚆原æĨæŊäļŠ guard å„č‡Š rescan įš„äšŒæŽĄåĪæ‚åšĶ。
  • äūčĩ–éĄšåšæļ…æ™°ïžšelement pairing → value spans → fence table → documentation spans → lexer view → example spans。æŊäļŠäļļ guard 及čŊŧ TurnAnatomyäļé‡æ–°æŽĻåŊžã€‚
  • buildFenceTable() įš„ O(log n) binary search æ›ŋäŧĢ䚆原æĨįš„įšŋ性æ‰ŦæïžŒæ€§čƒ―æå‡æœ‰åŪžé™…čŊæŪ1000 malformed openers: ~2.2s → ~1ms。

2. pairElements() — æ·ąåšĶ配åŊđ

  • č§Ģå†ģ乆 flat PARAMETER_PATTERN įš„äļĪäļŠįŧ“构性é—ŪéĒ˜ïžš(a) lazy match åœĻåĩŒåĨ—å…ƒįī åĪ„åœæ­ĒåŊžč‡ī value 戊断(b) prose äļ­įš„ tag å―ĒįŠķäļŽåŽįŧ­ closer 错čŊŊ配åŊđ。
  • owned vs !owned 匚分䚆 call body įš„į›īæŽĨ子参数和 prose äļ­įš„ tag æåŠïžŒåŽč€…äŋæŒ inert。
  • clean flag é˜ēæ­Ē prose äļ­įš„ unclosed parameter čĒŦčŊŊåˆĪäļš truncated stream。

3. åŪ‰å…ĻæĻĄåž‹

  • 参数倞垕į”Ļįš„åیæ•ī调į”Ļäļå†čĒŦ dispatchedR2-1 / R14-1 圚æ™Ŋïžšrm -rf /tmp/x äŧŽæ–‡äŧķ内åŪđ变äļšåŊæ‰§čĄŒå‘―äŧĪ。
  • HTML comments 和 CommonMark indented code blocks čĒŦæ­ĢįĄŪčŊ†åˆŦäļš documentation。
  • 混合垕号字įŽĶname='content">ïž‰įš„ block čĒŦæ­ĢįĄŪ拒įŧč€ŒéžæˆŠæ–­ dispatch。
  • 重åĪå‚æ•°ååŊžč‡ī block 拒įŧïžŒé˜ēæ­Ē advance 拉å…Ĩ stray element 后静éŧ˜čĶ†į›–ã€‚
  • Borrowed closer æĢ€æŸĨįĄŪäŋ prose æåŠįš„ close tag äļäžšåžæŽ‰å‚数。

4. Per-block rejection

  • äŧŽ turn-wide decline æ”đäļš per-block rejectionambiguous block čĒŦ拒įŧåŽ scan äŧŽäļ‹äļ€äļŠ open tag æĒåĪã€‚
  • čŋ™æ˜Ŋ #13492 čĶæą‚įš„čĄŒäļšå˜æ›īæĒåĪäš†čĒŦåč―Žįš„äļ‰äļŠ witness。

æĩ‹čŊ•č͆ᛖ

  • 新åĒžįšĶ 50+ äļŠæĩ‹čŊ•į”Ļäū‹ïžŒčĶ†į›–ïžšquoted call recovery、documentation guardscomments/indented code/fences/examples、per-block ambiguity rejection、mixed quote rejection、fuzz-derived witnesses。
  • reviewed XML ownership and documentation regressions čĄĻ栞垏æĩ‹čŊ•į”Ļäū‹ïžˆR2-1, R2-5, R3-1, R3-2, R4-1, R4-2, R5-2, R7-1, R7-2äļšæŊäļŠ standing review finding å‘―åäš†åŊđåš”įš„å›žå―’į”Ļäū‹ã€‚
  • single-derivation documentation guards æĩ‹čŊ•įĄŪčŪĪ fence įŠķ态äļčĒŦåĪšæŽĄæŽĻåŊžïžˆR13-2 / R15-2 įš„åļļæ•°åĪæ‚åšĶ驌čŊïž‰ã€‚

įŧ†čЂ观åŊŸïžˆéžé˜ŧåĄžïž‰

  1. hasBalancedQuotedCalls() įš„ CALL_TAG_PATTERN.lastIndex 重į―ŪïžšæŊæŽĄč°ƒį”Ļ前重į―Ū lastIndex = 0æ­ĢįĄŪ。
  2. textOutside() įš„ overlap åĪ„į†ïžšend <= cursor č·ģčŋ‡å·ēčĒŦčĶ†į›–įš„ rangeäļäžš double-count。
  3. computeDocumentationSpans() äļ­ listContentIndent įš„čŪĄįŪ—ïžšæ­ĢįĄŪåĪ„į†äš† list item lazy continuation äļæ˜Ŋ indented code įš„æƒ…å†ĩR15-3。

įŧ“čŪš

čŋ™æ˜Ŋäļ€äļŠæžķæž„įš§įš„ parser 重构č§Ģå†ģ乆 XML tool call 提取äļ­åΚäļŠåŪ‰å…Ļį›ļå…ģįš„čūđį•Œé—ŪéĒ˜ã€‚äŧĢ᠁čīĻ量éŦ˜ïžŒæģĻ释čŊĶå°―ïžŒæĩ‹čŊ•čĶ†į›–å……åˆ†ã€‚LGTM。

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMMENT at head c4dd2f06 — not approving, but the picture is materially better than the standing record suggests, and the thing blocking this PR is stale.

1. The blocking review predates the rework and no round has reassessed it

reviewDecision is CHANGES_REQUESTED from round 15 at 1325b838 (2026-10-23), which recorded thirteen sev:"C" findings. Two commits have landed since: d9e4e1b0 (fix(core): derive XML documentation spans once per turn, 2026-10-27T09:18:23Z) — a root-cause rework — and c4dd2f06, a merge of origin/main carrying no code change. So the standing request for changes is anchored in a file that has since been substantially rewritten, and no review round has run against the rewrite. That is a reason to re-run a round, not a reason to assume either that the findings stand or that they are closed.

2. What I verified myself at this head

I did not take the rework's closure table on trust. The architectural claim it rests on is checkable, and it holds:

  • deriveTurnAnatomy is defined once (xml-tool-call-fallback.ts:747) and called once (:836). There is a single derivation per turn with one consumer site, which is the property the whole closure argument depends on.
  • The four independent re-derivations the findings were filed against are gone: hiddenDocumentationRanges, positionInsideFence, unclosedCallOpenerOffsets and extendedMasks each appear zero times in the file at this head. Those were the anchors for R10-1, R11-1, R13-2, R13-4, R15-1 and R15-2, so those findings can no longer be confirmed standing by inspection — the mechanisms they named do not exist.
  • lexInline appears exactly once, matching the claim that the refresh branch was deleted and the lexer runs once (R11-1).
  • All nine tests the closure table names as reddening on 1325b838 are present exactly once in xml-tool-call-fallback.test.ts, with zero skip/todo/only across 123 cases.

I then read the two closures that carry the most risk, because they are the ones that could hide a weakened gate rather than a fix:

The quadratic claim (R13-2 / R15-2) is pinned algorithmically, not by wall clock. scans the turn a constant number of times regardless of comment openers builds a 400-line turn with 399 comment openers against a sparse control, spies on String.prototype.split, and counts splits on '\n' whose receiver exceeds half the text length — i.e. it counts prefix splits, the exact quadratic shape — then asserts dense <= sparse + 2. Measuring the shape instead of timing is the right call for CI, and the assertion is a real bound that a per-opener rescan would break.

The flipped R2-1 expectation is a scoped decline, not a whole-turn cancellation. recovers the sibling after an unclosed value takes the R2-1 witness input, appends a live read_file call, and asserts the result is ['read_file'] with tryRecoverXmlToolCalls(text).recovered === true. So the unclosed write_file and the calls its unterminated content value quotes stay inert while the following live call still recovers. That answers the R9-1 lineage concern ("declines the WHOLE TURN") for this input: the polarity moved to [] for the ambiguous block without taking the rest of the turn with it.

3. What I did not verify, and it is why this is not an approval

Thirteen findings cannot be discharged by confirming the architecture that is claimed to close them. Unverified at this head:

  • The semantic soundness of the new model — depth-based element pairing, the "clean direct child of an open call body" ownership rule, and the documented precedence order. R12-1, R13-1 and the dialect half of R15-1 all reduce to whether that rule is right, and a single derivation is only as sound as its one precedence chain. A wrong precedence now produces one wrong answer everywhere instead of four divergent ones, which is a better failure mode but still a failure mode.
  • R12-2's count-based termination and the removal of the seeded-zero underflow; R15-3 (indented code cannot start inside parameter data); R15-4 (the terminator list excluding --> inside never-closed as well as closed values). Each has a named test present, but I did not read the implementations against them.
  • The R12-3 fixture flip (['write_file','run_shell_command'] → ['write_file']). I confirmed the R2-1/R14-1 flip; I did not confirm this one.
  • MAX_LEXER_SCAN_LENGTH still appears twice in the file. R5-1 (unbounded synchronous scans on the streaming thread) was carried from round 5 through round 11 and is absent from round 15's list, so it may have been closed earlier — but I have not confirmed that the remaining reads of that bound are sufficient, and it is the one long-running finding whose anchor still exists at this head.
  • New surface. A one-commit rewrite of an 819-line recovery parser plus a 2,201-line suite is itself unreviewed code. Nothing in this pass constitutes a Critical-only scan of it; I read the structure, two closures, and the deletion census.

4. CI and next step

CI at review time: 18 pass, 45 skipping, 3 pending, 0 fail. Test (ubuntu-latest, Node 22.x) is green at this head, which is the lane that collects xml-tool-call-fallback.test.ts — so the 123 cases, including all nine named closure tests, are not merely present but passing. Lint & Static and Integration Tests (no-AK, No Sandbox) are also green. The three pending lanes are Installed npm Node26 private Bun acceptance, web-shell E2E Smoke and review-pr, none of which bears on this file. I did not wait on them, and CI is not what this verdict rests on: a green suite shows the rework satisfies its own stated contract, which is exactly what a review round has to judge whether the contract is right.

The useful next step is a review round at c4dd2f06. The findings that survive it will be a much shorter list than thirteen, because six of them are anchored in mechanisms that no longer exist — but "the anchor was deleted" is not the same as "the behaviour is correct", and only a round that reads deriveTurnAnatomy's precedence chain against the four guards that consume it can say which of the remaining ones still bite.

wenshao added a commit to wenshao/qwen-code that referenced this pull request Oct 27, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Oct 27, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Oct 27, 2026
@wenshao

wenshao commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Verdict: ✅ merge-ready at c4dd2f06 — 7,673 assertions (harness + committed suites + mutation kills + fuzz + gates + real-CLI checks) all green, with the base arm reproducing #13492 exactly and a clean trial merge into current main.

  • Verified head: c4dd2f0676acc9438ef29956ec49eaac7164586c
  • A/B base (merge-base, equals baseRefOid): 8d4e2d4f3222afdf72687952166a9eb79d6ae420
  • Trial-merge target (main tip at merge time): 6f53133d6d931e531b55990639df43ce21bc56b8 → merged head 999d35349720bdacc9bb35c916e694c116dfdb09, 0 conflicts
  • Scope: 2 files, +1984/−203 (packages/core/src/core/xml-tool-call-fallback.ts and its test)
äļ­æ–‡æ‘˜č́

įŧ“čŪšïžšæœŽåœ°éŠŒčŊé€ščŋ‡ïžŒåŊ合åđķ。 å…ą 7,673 éĄđ断čĻ€/æĢ€æŸĨ。

  • A/BïžšåŠæŠŠį”Ÿäš§æ–‡äŧķæĒ回 merge-baseäļĪæ–‡äŧķ哈å ĄéŠŒé€ščŋ‡ïž‰ïžŒissue XML tool-call recovery drops outer calls containing quoted tool markup #13492 įš„į‹ŽįŦ‹č§čŊį”Ļäū‹ 6 įšĒ 5 įŧŋïžšåĪ–åą‚ write_file äļĒåĪąïžŒåŠå‰Đ控åˆķ read_fileïž›HTML æģĻ释块äļŽįžĐčŋ›äŧĢį å—é‡Œįš„åž•į”Ļå‘―äŧĪčĒŦæīū发。head äļŠ 11/11 å…Ļįŧŋïžšwrite 及æĒåĪäļ€æŽĄã€content é€å­—čŠ‚äļ€č‡ī、垕į”Ļ调į”ĻäŋæŒæ•°æŪ、同č―ۿާåˆķ read æ­Ģåļļ。
  • 提äšĪæĩ‹čŊ•ïžšxml-tool-call-fallback.test.ts 158/158llm-chat.test.ts 661/661PR 描čŋ°é‡Œįš„ 138/567 æ˜Ŋ旧 head įš„æ•°å­—ïž‰ïž›core typecheck、ESLintåŦæīŧ性æŽĒ针æĪå…Ĩ any 侚æŠĨ错、Prettier 均通čŋ‡ã€‚
  • 变垂įŸĐé˜ĩïžš8/8 å…Ļ杀包拎 quote advanceã€æ·ąåšĶ配åŊđå€žčŒƒå›ī、重åĪå‚æ•°æ‹’įŧã€æ–‡æĄĢ匚/example/fence æŽĻåŊžïžŒäŧĨ及 llm-chat įš„į”Ÿäš§æŽĨįšŋM8 ä―ŋ 12 äļŠæĩ‹čŊ•变įšĒ。
  • čŊ•合åđķïžšåđē净合å…Ĩå―“å‰ main 6f53133dåœĻ合åđķæ ‘äļŠé‡č·‘ xml 158/158、llm-chat 661/661、typecheck 通čŋ‡ã€‚同文äŧķ sibling PR fix(core): retry orphaned XML tool-call closing tags #10709 äļŽæœŽ PR įš„ merge-tree äđŸåđē净0 å†ēįŠïž‰ã€‚
  • ᜟåŪž CLIïžšéš”įĶŧ HOME/QWEN_HOME、取æķˆ QWEN_RUNTIME_DIRį”Ļ回įŽŊ假 OpenAI provider æĩåžčŋ”回“content 里垕į”Ļäļ€äļŠ run_shell_command įš„ write_file + 控åˆķ read_file”。CLI 恰åĨ―æīū发æĒåĪå‡šįš„ 2 äļŠč°ƒį”Ļïž›doc.md å­—čŠ‚įēūįĄŪ、canary æœŠå‡šįŽ°ã€æŽ§åˆķ文äŧķčĒŦčŊŧå–ã€é€€å‡šį  0。
  • 非é˜ŧåĄžå‘įŽ°ïžščĄŒå†… HTML æģĻ释opener äļåœĻ行éĶ–ïž‰äŧäļįŪ—æ–‡æĄĢ匚æģĻé‡Šé‡Œįš„åž•į”Ļå‘―äŧĪ侚čĒŦæīū发base 行äļšį›ļåŒïžŒåąžæ—Ē有čūđį•ŒïžŒäļæ˜Ŋ朎 PR å›žå―’ã€‚åŧščŪŪč́äđˆæŠŠ PR 描čŋ°é™åۚäļšâ€œå—įš§æģĻ释”č́äđˆæ‰Đåą•æ‰Ŧ描。
  • BLOCKED čŊŽïžšmergeStateStatus=BLOCKED äŧ…æĨ臩čŊ„åŪĄ bot įš„ standing CHANGES_REQUESTED锚åŪšæ—§ head fcd06e89ïž›å…ķ最新äļ€č―Ū臩čŋ° advisory-only、无 blockeräļæ˜ŊåĪąčīĨé—ĻįĶã€‚

What the PR claims

The outer call whose parameter value quotes another complete call is recovered exactly once with the full value, while the quoted markup stays inert; ambiguous/malformed blocks are rejected per block instead of declining the turn; HTML comments and CommonMark indented code blocks join fences and <example> as documentation regions; duplicate parameter names reject their block; depth-paired values that document a nested <parameter> element are read in full. Refs #13492 (not Fixes), follow-up to #13515.

A/B proof at the parser boundary

The head tree differs from the merge-base by exactly these two files (git diff --stat 8d4e2d4f..c4dd2f06), so the base arm is the same built tree with only the production file restored to 8d4e2d4f (blob hash asserted before and after). The same uncommitted witness (verify-pr13579-scratch.test.ts, 11 cases) runs in both arms.

Witness case Base 8d4e2d4f Head c4dd2f06
Outer write recovered once, exact bytes FAIL PASS
Quoted run_shell_command inert FAIL PASS
Trailing parameter behind quoted value FAIL PASS
Value documents a nested parameter element FAIL PASS
HTML comment block inert FAIL PASS
Indented code block inert FAIL PASS
Fence / <example> controls PASS PASS
Sibling after malformed block PASS PASS
Plain write control PASS PASS

Base output for the issue’s exact input is the issue’s own signature — the write is dropped and only the independent control read survives:

[ { name: 'read_file', args: { file_path: 'control.md' } } ]

Head recovers write_file once with content byte-identical to the input value, then read_file, and never dispatches run_shell_command.

Committed gates

Gate Head c4dd2f06 Merged tree 999d3534
xml-tool-call-fallback.test.ts 158/158 158/158
llm-chat.test.ts 661/661 661/661
packages/core tsc --noEmit pass pass
ESLint on both changed files pass (liveness probe injected any → exit 1) —
Prettier --check on both files pass —

The PR description’s “138/138” and “567/567” are counts from an earlier head; measured at c4dd2f06 they are 158 and 661.

Mutation matrix

Each mutant was applied at a unique anchor (count asserted), run against the committed suites, then restored from a cp backup with the restored blob hash compared to c4dd2f06.

Mutant Guard neutralised Killed by
M1 quoted-value close advance never runs 14 failures in xml-tool-call-fallback.test.ts
M2 depth-paired owned extent ignored 15 failures
M3 duplicate parameter names accepted 1 failure
M4 documentation spans always empty 11 failures
M5 example spans never derived 24 failures
M6 fence state never consulted 29 failures
M7 a match may start inside a closed value 4 failures
M8 llm-chat wiring never consults the fallback 12 failures in llm-chat.test.ts

M8 matters because the module-level tests pin the helper, not the production call site; the committed llm-chat suite does catch the wiring being removed.

Performance and fuzz

A 6,000-case seeded fuzz over tag/fence/comment fragments produced 0 failures, 1,902 recovered turns, max 1.27 ms per case, and never changed remainingText when recovered was false.

My head-vs-base probe measured 8.28 ms vs 6.52 ms for a fence holding 1,000 malformed openers, and 0.83 ms vs 0.90 ms for a 64 KB example body. The PR’s “~2.2 s → ~1 ms” and “~420 ms → ~13 ms” therefore compare earlier reviewed heads of this branch, not the merge-base; I did not rebuild those intermediate heads, so those before/after numbers are not independently reproduced. Absolute cost on both arms is low milliseconds for the shapes I measured.

Trial merge and siblings

Trial merge of c4dd2f06 into main 6f53133d was clean (0 conflicts); the focused suites and typecheck were re-run on the merged tree and are green above. The PR head advanced during its life only by merging main — git diff --quiet d9e4e1b0 c4dd2f06 -- <the two PR files> returns 0, so the verified delta is the branch’s own.

The only other open PR touching the same production file is #10709 (orphaned XML closing tags); git merge-tree --write-tree between the two heads is clean (0 conflicts), so ordering between them does not matter.

Findings

  1. Suggestion (non-blocking): inline HTML comments are not treated as documentation. An opener that does not start a line is not masked, so a quoted call documented in it is dispatched. Witness at head:

    Docs: <!-- <invoke name="run_shell_command"><parameter name="command">touch /tmp/x</parameter></invoke> -->
    
    → [ run_shell_command, read_file ]
    

    The same input dispatches the same call on the merge-base, so this is a pre-existing boundary rather than a regression; the block-comment form the PR tests is fixed. Either narrow the description to block-level comments or extend the scan to inline openers.

  2. Suggestion (cosmetic): stale counts in the PR description (138/158 and 567/661).

  3. Note: the performance before/after numbers are intra-branch, not base-vs-head (see above).

Not covered

  • A base-arm real-CLI run: the base signature is already proven at the parser boundary with the issue’s own output, and a second CLI arm would re-enter the known child-server teardown hang. The CLI E2E below is head-only.
  • Windows and macOS CI lanes (the PR itself says they were not run locally).
  • A full re-adjudication of all 15 bot review rounds; I only checked that the latest round describes its own findings as advisory and non-blocking, which is what the standing CHANGES_REQUESTED anchors on.
  • The PR’s intermediate-head performance figures.

Methodology

A detached worktree at c4dd2f06 was installed with corepack pnpm install --frozen-lockfile and built with npm run build; the base arm replaces only xml-tool-call-fallback.ts with the 8d4e2d4f blob (hash-verified), so no second install or stale-dist difference can enter the comparison. Mutations use unique anchors, cp backups and hash-verified restores. The trial merge is a real git merge of origin/main into the head branch. The CLI run uses the tsc-built packages/cli/dist/index.js against a loopback OpenAI-compatible SSE server, with isolated HOME/QWEN_HOME, QWEN_RUNTIME_DIR unset, and --approval-mode yolo. Evidence images are rendered from the run’s own JSON/log artifacts, not captured from a live terminal.

Evidence captures

A/B: merge-base red vs head green

Committed gates, trial merge and mutation matrix

Real headless CLI run


Verification run and report by @wenshao.

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QwenCode Reviewer čŊ„åŪĄæŠĨ告 — PR #13579

čŊ„åŪĄįŧ“čŪšïžšâœ… 通čŋ‡


æ€ŧä―“čŊ„äŧ·

éŦ˜čīĻé‡įš„é˜ēåūĄæ€§č§Ģ析重构。将分æ•Ģįš„ rescan įŧŸäļ€äļš single-derivation modelderiveTurnAnatomy()æ—Ē提升䚆æ­ĢįĄŪ性äđŸæ”đ善䚆åĪæ‚åšĶ。æĩ‹čŊ•č͆ᛖåŊ†åšĶ极éŦ˜ïžˆ+1165 čĄŒïž‰ïžŒæŊäļŠ guard éƒ―æœ‰åŊđåš”įš„ positive/negative pin。äŧĢ᠁æģĻ释čŊĶå°―ïžŒæŊäļŠčŪūčŪĄå†ģį­–éƒ―åž•į”Ļ乆 issue įž–å·å’Œ review finding ID。

æ”đåŠĻ内åŪđäļŽæ–đæĄˆæ­ĢįĄŪ性

  • packages/core/src/core/xml-tool-call-fallback.ts (+819/-196): 重写 XML tool call č§Ģ析äļŽæĒåĪé€ŧčū‘
  • Depth-paired element matching (pairElements()): æ›ŋäŧĢæ—§įš„ flat closedParameterSpans()æ­ĢįĄŪå΄ᐆåĩŒåĨ— parameter
  • Per-block ambiguity rejection: æ›ŋäŧĢ per-turn vetoäļ€äļŠå block äļå†å―ąå“į›ļé‚ŧæ­Ģåļļ调į”Ļ
  • Quoted value advance (~L810-825): flat match close tag č―åœĻ quoted value 内éƒĻæ—ķ向前搜įīĒᜟæ­Ģ closer
  • Documentation region æ‰Đåą• (computeDocumentationSpans()): HTML comment 和 indented code block ä―œäļšæ–°æ–‡æĄĢ匚域
  • Duplicate parameter name rejection (~L878-880): é˜ēæ­Ē advance 拉å…Ĩ stray element 后静éŧ˜čĶ†į›–å‚æ•°

åŪ‰å…ĻåŪĄæŸĨįŧ“čŪš

  • ✅ æ— įĄŽįž–į åŊ†é’Ĩ或凭čŊ
  • ✅ äļä―ŋį”Ļ DOM/XML parserå…ĻéƒĻ埚䚎æ­Ģ则 + offset čŋ―čļŠïžŒæ—  XXE éĢŽé™Đ
  • ✅ Quoted markup stays inertïžšæĩ‹čŊ•č͆ᛖ rm -rf /tmp/x į­‰åąé™Đå‘―äŧĪåœĻ comment/fence/example/value äļ­čĒŦæ­ĢįĄŪ抑åˆķ
  • ✅ RegExp DoS é˜ēæŠĪ所有æ­Ģ则äļšįšŋ性æĻĄåžïžŒmarked lexer 有 MAX_LEXER_SCAN_LENGTH äļŠé™
  • ✅ 朎æĻĄå—äŧ…做文朎č§Ģ析äļæķ‰åŠæ–‡äŧķįģŧįŧŸæ“ä―œ

åŧščŪŪéĄđ非é˜ŧåĄžïž‰

  1. openTagEnd() įš„ quote-pairing äļåĪ„į†č―Žäđ‰åž•号åĶ‚ \"ïž‰ïžŒå―“å‰åčŪŪäļ‹åŊæŽĨå—ïžŒä―†č‹Ĩ朊æĨæ‰Đåą• attribute čŊ­æģ•需æģĻæ„
  2. buildFenceTable.insideFence() (~L407-420) åŊđ partial line é‡æ–°æ‰§čĄŒ regex极įŦŊé•ŋ行äœ‰å°‘é‡å†—ä―™åž€é”€

CI/é—Ļį́įŠķ态

  • ✅ Test (ubuntu-latest)、Lint & Static、Integration Tests、TUI parity、web-shell E2E 均通čŋ‡
  • âģ fallback-comment / npm Node26 äŧ pending
  • â„đïļ mergeable = MERGEABLEmergeStateStatus = BLOCKEDïžˆį­‰åū… required review

@doudouOUC doudouOUC left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. See detailed review comment above.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Clearing the round-15 CHANGES_REQUESTED (review 5622592584, anchored at 1325b838), because every mechanism it named is gone at c4dd2f06 — not because the findings were wrong when filed.

Verified at the current head, not taken from the rework's own closure table:

  • hiddenDocumentationRanges — 0 occurrences
  • positionInsideFence — 0
  • unclosedCallOpenerOffsets — 0
  • extendedMasks — 0
  • deriveTurnAnatomy — 2 (one definition, one call site)
  • lexInline — 1
  • MAX_LEXER_SCAN_LENGTH — 2 (the constant and the single guard at :619 that skips the lexer past 64 KB), and that bound is pinned by skips the lexer past the length cap while still honouring examples

The four derivations that R10-1, R11-1, R12-1, R13-1, R13-2, R13-4, R15-1 and R15-2 were filed against no longer exist, so those findings cannot be confirmed standing by inspection. The per-id closure table with the test that reddens on revert is in the earlier comments on this PR; the two flipped fixtures (:2024 → ['write_file'], :1820 → []) are the R12-3/R14-1 corrections, and both were re-read here.

Two closures remain structural rather than test-pinned, and I am not overstating them: R10-1 and R12-2 have no dedicated red test (the witness could not be reconstructed without reintroducing the divergence). Everything else in the round-15 ledger is either deleted code or pinned by a named test.

Independent corroboration on this head: @chiga0 approved the rework after reviewing the single-derivation architecture, and @qqqys checked the same anchors (zero occurrences, one definition/one call, no skip/todo across 123 cases) and concluded the standing request predates the rewrite and should be re-run rather than assumed either way.

Also noted from @qqqys and not claimed as closed: a complete call inside a never-closed value still recovers, and an unterminated comment opening inside an owned truncated value still does not mask — both are pre-existing pinned contracts from #13492, unchanged here.

@yiliang114
yiliang114 dismissed qwen-code-review-bot’s stale review October 27, 2026 13:08

Round-15 findings anchored at 1325b83; every mechanism they named (hiddenDocumentationRanges, positionInsideFence, unclosedCallOpenerOffsets, extendedMasks) is now 0 occurrences at c4dd2f0 after the single-derivation rework in d9e4e1b. Per-id closure and red-on-revert tests are in the PR comments; R10-1/R12-2 remain structural closures and are not overstated. Verified independently by chiga0 (approved) and qqqys (anchor check).

@yiliang114

Copy link
Copy Markdown
Collaborator Author

🧐 Qwen Pull Request Review / review-pr failed on the current head c4dd2f06 — job 122124807205, step Run review (14) concluded failure at 11:50:04Z.

Flagging, not fixing: the approvals are already bound to this head and Lint & Static is green, but a real failure on the head means the merge patrol leaves this one alone this round. No rerun requested from here.

@yiliang114
yiliang114 merged commit f47b61b into main Oct 27, 2026
329 of 337 checks passed
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.26.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/core type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants