Skip to content

fix(core): refuse substitutions hidden in pattern words and heredoc bodies - #10029

Closed
TianYuan1024 wants to merge 2 commits into
QwenLM:mainfrom
TianYuan1024:fix/hidden-substitution-leaves
Closed

fix(core): refuse substitutions hidden in pattern words and heredoc bodies#10029
TianYuan1024 wants to merge 2 commits into
QwenLM:mainfrom
TianYuan1024:fix/hidden-substitution-leaves

Conversation

@TianYuan1024

@TianYuan1024 TianYuan1024 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Closes two places where tree-sitter-bash hands back a single leaf node, so the classifier's substitution walk finds nothing to collect while bash still runs what is inside.

Expansion pattern words. The pattern of ${v%%…}, ${v%…}, ${v##…}, ${v#…}, ${v^^…}, ${v^…}, ${v,,…}, ${v,…} is one leaf. So is each half of ${v/pat/rep}, and the operand of ${v:-…}, ${v:=…}, ${v:?…}, ${v:+…}. A substitution written there yields no node of its own, so echo ${x%%$(rm -rf build)} classified read-only — and would run unattended in Plan Mode. Since the collection pass already found nothing, an opener still present in the expansion's text is exactly that hidden channel, and the leaf is refused on the text.

Heredoc bodies. A body is one leaf too — always for <<-, and for << whenever nothing inside it parsed — and bash expands it before feeding it to stdin. Expansion there follows double-quote rules, so $(…), backticks and ${v@P} run while <(…) does not; the body is refused for the first three only. A quoted delimiter (<<'EOF', <<"EOF", <<\EOF) makes the body inert and is exempted.

${v@P} is handled in both, because a prompt expansion runs any $(…) held in the variable's value, and in a pattern word or a body it is a leaf the @/P child-adjacency check never sees. Its regex is deliberately not anchored to a brace-free span: ${a[${b}]@P} nests a brace, and a [^{}]* bridge stops at it.

Why it's needed

These are the inputs where the AST gives less information than bash acts on. The classifier's whole contract is that a read-only verdict means nothing executes — and in Plan Mode a read-only verdict means the command runs with no confirmation at all. echo ${x%%$(rm -rf build)} is a deletion that never appears as a command node.

Reviewer Test Plan

How to verify

cd packages/core && npx vitest run src/utils/shellAstParser.test.ts

 Test Files  1 passed (1)
      Tests  598 passed (598)

Before this change, on main:

await isShellCommandReadOnlyAST('echo ${x%%$(rm -rf build)}')       // true
await isShellCommandReadOnlyAST('echo ${x/a/$(rm -rf build)}')      // true
await isShellCommandReadOnlyAST('cat <<-EOF\n\t$(rm -rf build)\n\tEOF')  // true

After, all three are false. The exemptions still hold:

await isShellCommandReadOnlyAST("cat <<'EOF'\n$(rm -rf build)\nEOF")  // true — quoted delimiter
await isShellCommandReadOnlyAST('cat <<EOF\n<(rm -rf build)\nEOF')    // true — bash never runs <() here
await isShellCommandReadOnlyAST('echo ${x%%.txt}')                    // true — no substitution

Mutation-verified: neutralising the three leaf regexes fails 28, 10 and 2 tests respectively, and dropping the quoted-delimiter exemption fails 1.

Note: src/permissions/permission-manager.test.ts has 2 failures on main at this commit (resolveToolName exhaustiveness (#9827), about ReportFindings). They are unrelated to this PR and reproduce on a clean origin/main checkout.

Evidence (Before & After)

N/A — no TUI change. The user-visible difference is that these forms now prompt instead of running unattended.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Risk & Scope

  • Main risk: these are text checks on leaf nodes, not structural analysis, so they over-refuse by construction — an expansion whose pattern merely contains the characters $( is refused whether or not bash would expand it. They are fallbacks for sites the node walk cannot reach at all, so the cost is a prompt and the alternative is silence; but a user with an unusual pattern word will see a confirmation they did not before.
  • Not validated / out of scope: the pre-existing permission-manager failures noted above. The deprecated regex fallback in shellReadOnlyChecker.ts is untouched.
  • Breaking changes: none.

Linked Issues

No issue to close. This is a self-contained correctness fix carved out of #9950 — the hidden-substitution leaves surfaced while that PR was under review, and it is filed separately so it can land on its own merits. Referenced without a closing keyword: #9950.

中文说明

这个 PR 做了什么

修复两处 tree-sitter-bash 只返回单个叶子节点、导致分类器的替换遍历什么都收集不到、而 bash 仍会执行其中内容的情形。

展开的模式词。 ${v%%…}${v%…}${v##…}${v#…}${v^^…}${v^…}${v,,…}${v,…} 的模式部分是一个叶子;${v/pat/rep} 的两半、以及 ${v:-…}${v:=…}${v:?…}${v:+…} 的操作数同样如此。写在那里的替换不会产生自己的节点,于是 echo ${x%%$(rm -rf build)} 被判为 read-only,在 Plan Mode 下会无人值守执行。既然收集阶段已经一无所获,展开文本中仍然存在的开启符恰恰就是那条隐藏通道,因此按文本拒绝该叶子。

heredoc body。 body 同样是一个叶子(<<- 恒为如此,<< 在其内部无内容被解析时亦然),而 bash 会先展开它再送入 stdin。此处的展开遵循双引号规则:$(…)、反引号、${v@P} 会执行,<(…) 不会——因此只对前三者拒绝。带引号的分隔符(<<'EOF'<<"EOF"<<\EOF)使 body 惰性化,予以豁免。

${v@P} 两处都处理,因为提示符展开会运行变量值中携带的任何 $(…),而在模式词或 body 中它是一个叶子,@/P 子节点相邻检查永远看不到它。其正则刻意不限定为无花括号跨度:${a[${b}]@P} 内嵌了花括号,[^{}]* 桥接会在那里断掉。

为什么需要它

这些正是 AST 提供的信息少于 bash 实际行为的输入。分类器的全部契约是「read-only 意味着什么都不会执行」——而在 Plan Mode 下,read-only 意味着命令完全不弹窗直接执行。echo ${x%%$(rm -rf build)} 是一次从不以命令节点形式出现的删除。

验证方式

见上文英文部分。变异测试:把三个叶子正则置空分别导致 28、10、2 个测试失败;去掉带引号分隔符的豁免导致 1 个失败。

注:本提交所基于的 main 上,permission-manager.test.ts 本身有 2 个与本 PR 无关的失败。

风险与范围

  • 主要风险: 这些是对叶子节点的文本检查而非结构分析,因此按构造就会过度拒绝——模式词只要包含 $( 字符就会被拒绝,无论 bash 是否真的会展开。它们是节点遍历根本到不了的位置的兜底,代价是弹窗,而另一个选择是静默;但使用不寻常模式词的用户会看到此前没有的确认框。
  • 明确不在范围内: 上述 main 自带的失败;已废弃的正则回退路径不动。
  • 破坏性变更: 无。

关联 Issue

没有需要关闭的 issue。这是从 #9950 中拆分出来的一处独立正确性修复——隐藏替换叶子节点是在那个 PR 评审过程中发现的,单独提出以便独立评审合入。仅作引用、不带关闭关键字:#9950

…odies

Two places where tree-sitter-bash yields a single leaf node, so the
substitution walk finds nothing to collect while bash still runs what is
inside.

The pattern word of `${v%%…}`, `${v%…}`, `${v##…}`, `${v#…}`, `${v^^…}`,
`${v^…}`, `${v,,…}`, `${v,…}` is one leaf, and so is each half of
`${v/pat/rep}` and the operand of `${v:-…}`, `${v:=…}`, `${v:?…}`, `${v:+…}`.
`echo ${x%%$(rm -rf build)}` therefore classified `read-only` and would have
run unattended. Since the collection pass found nothing, an opener still
present in the expansion text is exactly that hidden channel, so the leaf is
refused on the text.

A heredoc body is one leaf too — always for `<<-`, and for `<<` whenever
nothing inside it parsed — and bash expands it before feeding it to stdin.
Expansion there follows double-quote rules, so `$(…)`, backticks and `${v@P}`
run while `<(…)` does not; the body is refused for the first three only. A
quoted delimiter (`<<'EOF'`, `<<"EOF"`, `<<\EOF`) makes the body inert and is
exempted.

`${v@P}` is included in both because a prompt expansion runs any `$(…)` held in
the variable's value, and in a pattern word or a body it is a leaf that the
`@`/`P` child-adjacency check never sees. The regex is deliberately not
anchored to a brace-free span: `${a[${b}]@p}` nests a brace, and a `[^{}]*`
bridge stops at it.

These are leaf fallbacks for sites the node walk cannot reach, so over-refusing
costs at most a prompt. Mutation-verified: neutralising the three regexes fails
28, 10 and 2 tests respectively, and dropping the quoted-delimiter exemption
fails 1.
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — the Linked Issues heading is absent, but the before/after reproduction is written out inline, which is what that section exists to establish.

Problem: observed, not theoretical. The PR names concrete commands (echo ${x%%$(rm -rf build)}, tab-stripped <<- heredoc bodies) and claims they classify read-only on main. I traced the base classifier statically and the hole is real: when no command_substitution/process_substitution node is collected, the zero-substitution branch only runs the @/P child-adjacency check and then returns read-only — nothing inspects leaf pattern words or heredoc bodies, even though bash expands both. Since the verdict feeds permission-manager (auto-approve), plan-mode-shell-policy (runs unattended), shell/monitor tools, the speculation gate, and memory-scoped config, a false read-only here means code execution with no confirmation.

Direction: aligned. The classifier's entire contract is "read-only means nothing executes," and this closes a hole in exactly that contract. Peer agents are actively fixing the same class of issue (e.g. claude-code CHANGELOG 2.1.x: "Fixed hook if conditions … firing on unrelated Bash commands when the command contained $() or backtick command substitution", plus several Bash/PowerShell permission-check hardening entries) — no direct reference to this exact case, but the area is clearly relevant.

Size: core paths touched (packages/core/src/utils/shellAstParser.ts +58 production lines; shellAstParser.test.ts +212 test lines). Well under any escalation threshold; no generated/schema lines.

Approach: scope feels right — three leaf regexes plus a quoted-delimiter exemption, all confined to the branch that already found nothing structurally, so any command with a real substitution node keeps its existing unknown-floored path. The documented over-refusal tradeoff (text match on openers, cost = one extra prompt) is the correct direction for a safety classifier. No unrelated changes in the diff.

Risk: no elevated risk signals — neither changed file matches the revert-correlated high-risk paths. Note for the reviewer: this is Tier 2 core territory, so the bar below is 100% confidence, and the behavioral claims (tree-sitter grammar quirks: leaf pattern words, <<- tab bodies, quoted-delimiter inertness) are pinned by tests that this PR's own CI must confirm.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 缺少 Linked Issues 小节,但 before/after 复现已直接写在正文里,该小节要证明的内容已经具备。

问题:已观测而非理论性的。PR 给出了具体命令(echo ${x%%$(rm -rf build)}、tab 缩进的 <<- heredoc body),并声称在 main 上被判为 read-only。我静态追踪了基线分类器,漏洞属实:当没有收集到任何 command_substitution/process_substitution 节点时,零替换分支只做 @/P 子节点相邻检查就直接返回 read-only——叶子模式词和 heredoc body 都没有人检查,而 bash 对两者都会展开。该判定被 permission-manager(自动批准)、plan-mode-shell-policy(无人值守执行)、shell/monitor 工具、投机门控和 memory-scoped config 消费,这里的误判 read-only 意味着代码在零确认下执行。

方向:对齐。分类器的全部契约就是「read-only 意味着什么都不会执行」,这个 PR 正是堵这个契约上的洞。同类 agent 也在持续修复同类问题(如 claude-code CHANGELOG 中关于 $()/反引号命令替换触发权限误判的修复,以及多条 Bash/PowerShell 权限检查加固记录)——没有与本例完全对应的条目,但该领域明显相关。

规模:触及核心路径(shellAstParser.ts 生产代码 +58 行;shellAstParser.test.ts 测试 +212 行),远低于任何升级阈值;无生成/schema 代码。

方案:范围合理——三个叶子正则加一个带引号分隔符豁免,全部限制在"结构上已经一无所获"的分支内,因此凡是存在真实替换节点的命令仍走原有的 unknown 保底路径。文档中写明的过度拒绝权衡(按文本匹配开启符,代价是多一次弹窗)对安全分类器来说是正确的方向。diff 中没有无关改动。

风险:无升级风险信号——两个改动文件均未命中与 revert 相关的高风险路径。提醒 reviewer:这属于 Tier 2 核心模块,下面的标准是 100% 确信;其中关于 tree-sitter 语法行为的主张(叶子模式词、<<- tab body、带引号分隔符惰性化)由测试钉住,需要本 PR 自己的 CI 来证实。

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review. Independent baseline first: given only the title and the "why", my own approach would have been exactly this shape — stay inside the zero-substitution branch of evaluateSubstitutions (the only place a false read-only can escape) and add fail-closed text checks at the two leaf sites bash expands but tree-sitter never decomposes: parameter-expansion pattern words and heredoc bodies, with quoted-delimiter bodies exempted. The PR matches that proposal; I found no simpler path it missed.

What I verified against the code:

  • One-way ratchet. Both new checks live inside the substitutions.length === 0 branch. Any command where a real command_substitution/process_substitution node exists keeps the existing mergeSafety('unknown', …) floor, so nothing that prompted before can newly read as read-only — the change can only turn read-only into unknown/write.
  • Pattern words: openers checked are $(, backtick, <(, >( plus ${…@P}. The PROMPT_EXPANSION regex is deliberately unanchored (\$\{[\s\S]*@P) so nested braces like ${a[${b}]@P} can't slip past a [^{}]* bridge — the comment says so, and the cost is at most an extra prompt. Correct trade for a safety classifier.
  • Heredoc bodies: HEREDOC_SUBSTITUTION rightly omits <( — bash doesn't run process substitutions in a heredoc body (double-quote rules), so including it would refuse inert text. Quoted delimiters (', ", \) exempt the body; a missing heredoc_start child fails closed. <<- tab-stripped bodies — the always-leaf case — are caught by the text check since the node walk sees nothing there.
  • Consumers: the verdict feeds permission-manager, plan-mode-shell-policy, the shell/monitor tools, speculationToolGate, and memory-scoped agent config. All treat unknown as "prompt", so newly refused spellings surface as a confirmation dialog, not a break — matching the Risk & Scope note.
  • Tests (212 lines): each trim/case operator, both halves of ${v/pat/rep}, the value operators, tab-stripped <<- bodies, quoted-delimiter exemptions, the inert <(-in-body case, and the heredoc delimiter itself are pinned apart by expected category (write where a real node parses, unknown where only the leaf regex sees it), so a silent category change fails rather than passes. Negative cases keep ${HOME%%/*} read-only. Reuses the existing collectDescendants helper; style matches the file's existing regex constants. No critical findings, no convention violations.

Not verified statically: the grammar-behavior claims (leaf pattern words, <<"EOF" inertness) can't be re-derived without executing the parser, which this review does not do. They are pinned by tests, and this PR's own CI runs them against the real wasm grammar — see below.

Test evidence — the PR's own CI on eb94b98. The Test jobs are red, but the evidence says environmental, not PR-caused:

  • Inside the failing ubuntu job, the suite this PR touches is green: ✓ src/utils/shellAstParser.test.ts (598 tests) 1862ms.
  • The failures scatter across unrelated suites — serve/server, run-qwen-serve, daemon-git-worktree-guard (48), review/scratch-tree (36), review/lib/worktree (32), fs/paths, readManyFiles, node-repl.semantics, SessionMessageHandler, dws-event-stream — fs/git/serve territory, with no mechanism connecting them to two regexes in the shell classifier. The job log shows the runner exhausting disk: repeated disk full / ENOSPC entries (failed to write channel worker pidfile metadata: disk full).
  • Corroboration from other branches in the same window: sibling PR fix(core): analyse the statements and redirects nested in a heredoc node #10028 (different diff) shows the same windows Test failure, and PR ci: point the windows test job's temp at a short-alias-free path #10034 is an open CI fix for the windows temp-path issue.
  • permission-manager.test.ts fails exactly the two tests the PR description flagged as pre-existing on main — confirmed from the log's assertions (resolveToolName exhaustiveness (#9827) / ReportFindings alias), unrelated to this change.
Check Conclusion
Test (ubuntu-latest, Node 22.x) ❌ failure — environmental (runner disk full; PR's own suite green)
Test (windows-latest, Node 22.x) ❌ failure — environmental (same window as sibling PRs)
Test (macos-latest, Node 22.x) ⏳ queued
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ⏳ in progress

Sandboxed verification would settle the remaining behavioral claim end-to-end: @qwen-code /verify — that the three headline spellings (echo ${x%%$(rm -rf build)}, ${x/a/$(rm -rf build)}, tab-stripped <<- body) actually flip the verdict between the base build and this PR, rather than only inside the unit suite. The author tested on macOS only per the PR body, so a sponsored run (maintainer-triggered; it carries a pre-execution risk screen and a full workspace wipe) is the lane if a maintainer wants product-level A/B proof — reading the resulting report with the same skepticism as the fork's own CI.

中文说明

代码审查。 先说独立基线:只看标题和动机,我自己的方案与此完全同形——留在 evaluateSubstitutions 的零替换分支内(误判 read-only 只能从这里漏出),在 bash 会展开但 tree-sitter 不分解的两处叶子位置加失败关闭的文本检查:参数展开的模式词与 heredoc body,并对带引号分隔符的 body 豁免。PR 与该方案一致,我没有找到它遗漏的更简路径。

对照代码核实的内容:

  • 单向收紧。 两个新检查都在 substitutions.length === 0 分支内。凡是存在真实 command_substitution/process_substitution 节点的命令仍走原有 mergeSafety('unknown', …) 保底,因此之前会弹窗的命令不可能新判为 read-only——本改动只会把 read-only 变成 unknown/write
  • 模式词: 检查的开启符为 $(、反引号、<(>( 以及 ${…@P}PROMPT_EXPANSION 刻意不加锚定(\$\{[\s\S]*@P),使 ${a[${b}]@P} 这类嵌套花括号不会从 [^{}]* 桥接处漏过——注释已说明,代价至多多一次弹窗。对安全分类器是正确取舍。
  • Heredoc body: HEREDOC_SUBSTITUTION 正确地不包含 <(——bash 在 heredoc body(双引号规则)中不执行进程替换,加进去只会拒绝惰性文本。带引号分隔符('"\)豁免 body;找不到 heredoc_start 子节点时失败关闭。<<- 的 tab 缩进 body(恒为叶子的形态)由文本检查兜住,因为节点遍历在那里什么也看不见。
  • 消费方: 该判定被 permission-managerplan-mode-shell-policyshell/monitor 工具、speculationToolGate 和 memory-scoped agent config 消费。它们都把 unknown 当作"弹窗",因此新被拒绝的写法表现为一次确认框而非破坏——与 Risk & Scope 的说明一致。
  • 测试(212 行): 逐一钉住每个裁剪/大小写操作符、${v/pat/rep} 的两半、值操作符、tab 缩进的 <<- body、带引号分隔符豁免、body 中惰性的 <(,以及 heredoc 分隔符本身,并按预期类别区分(能解析出真实节点的记 write,只有叶子正则可见的记 unknown),使类别静默变化会失败而非蒙混过关。负例保证 ${HOME%%/*} 仍为 read-only。复用既有 collectDescendants 帮助函数;风格与文件内既有正则常量一致。无关键问题,无规范违规。

静态无法核实的部分:tree-sitter 语法行为(叶子模式词、<<"EOF" 惰性化)无法在不执行解析器的情况下重新推导,本审查不执行代码。这些主张由测试钉住,而本 PR 自己的 CI 会用真实 wasm 语法运行它们——见下。

测试证据——本 PR 在 eb94b98 上的 CI。 Test 任务是红的,但证据指向环境问题而非 PR 所致:

  • 在失败的 ubuntu 任务内部,本 PR 触及的套件是绿的:✓ src/utils/shellAstParser.test.ts (598 tests) 1862ms
  • 失败散布于无关套件——serve/serverrun-qwen-servedaemon-git-worktree-guard(48 个)、review/scratch-tree(36)、review/lib/worktree(32)、fs/pathsreadManyFilesnode-repl.semanticsSessionMessageHandlerdws-event-stream——全是文件系统/git/serve 领域,与 shell 分类器里的两个正则之间不存在作用机制。任务日志显示 runner 磁盘耗尽:反复出现 disk full / ENOSPCfailed to write channel worker pidfile metadata: disk full)。
  • 同一时间窗内其他分支的旁证:兄弟 PR fix(core): analyse the statements and redirects nested in a heredoc node #10028(不同的 diff)出现相同的 windows Test 失败;ci: point the windows test job's temp at a short-alias-free path #10034 正是针对 windows 临时路径问题的在途 CI 修复。
  • permission-manager.test.ts 恰好失败于 PR 描述中指认的 main 自带失败——已从日志断言核实(resolveToolName exhaustiveness (#9827) / ReportFindings 别名),与本改动无关。

(CI 表格见上方标记区域,完成后由 finalize 工作流自动更新。)

沙箱验证可以端到端落定剩余的行为主张:@qwen-code /verify——确认三个代表性写法(echo ${x%%$(rm -rf build)}${x/a/$(rm -rf build)}、tab 缩进的 <<- body)在基线构建与本 PR 之间确实翻转判定,而不只是在单测套件内成立。作者按 PR 正文仅在 macOS 上测试过,因此如果 maintainer 想要产品级 A/B 证据,可选通道是 sponsored run(由 maintainer 触发,带执行前风险筛查与完整工作区清理)——对产出的报告应与对 fork 自身 CI 日志同样的审慎态度阅读。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — a clean, one-directional safety fix with unusually disciplined test pinning; the only reservation is CI settling (two Test jobs red from runner disk exhaustion, macOS job still queued), not the code.

Stepping back: my independent proposal for this problem — fail-closed text checks at exactly the leaf sites bash expands but tree-sitter never decomposes, scoped to the zero-substitution branch — is what this PR implements, and I didn't find a simpler path it missed. The hole is real (I traced it in the base classifier), the fix can only move verdicts toward prompting, and the tests pin each spelling by expected category so future grammar or refactor drift fails loudly instead of silently reopening the hole. The comments explain the non-obvious why (the unanchored @P regex, the omitted <( in heredoc bodies) — in six months this reads like someone who knew exactly what they were doing. Every line in the diff serves the stated goal.

The author has other shell-parser PRs open right now; judged on its own, this one earns its place — it closes a concrete silent-execution path in Plan Mode rather than rearranging code.

Why not 5/5 and why no approval this run: the PR's CI hasn't finished settling on the reviewed commit. Once it lands green on that commit the approval follows automatically; if anything lands red or the head moves, the deferral flags it instead.

中文说明

置信度:4/5 —— 一个干净的、单向收紧的安全修复,测试钉住做得异常严谨;唯一的保留是 CI 尚未落定(两个 Test 任务因 runner 磁盘耗尽而红,macOS 任务仍在排队),与代码本身无关。

退一步看:我对这个问题的独立方案——在 bash 会展开但 tree-sitter 不分解的叶子位置、且仅限于零替换分支内做失败关闭的文本检查——正是这个 PR 的实现,我没有找到它遗漏的更简路径。漏洞真实存在(我在基线分类器中追踪到了),修复只会把判定推向"弹窗"方向,测试按预期类别钉住每种写法,使未来语法升级或重构导致的漂移会大声失败而不是静默重开漏洞。注释解释了非显而易见的"为什么"(不加锚定的 @P 正则、heredoc body 中省略的 <()——六个月后读起来像出自完全清楚自己在做什么的人。diff 中每一行都服务于既定目标。

作者目前还有其他 shell 解析器相关的 PR 在途;单独评判,这个 PR 有其价值——它堵上的是 Plan Mode 中一条具体的静默执行通道,而不是重排代码。

为什么不是 5/5、为什么本次运行不直接批准:该 PR 的 CI 在被审查提交上尚未落定。一旦在该提交上全绿,批准会自动跟上;若有任何任务变红或 head 移动,延期机制会标记出来而不是放行。

Qwen Code · qwen3.8-max

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

@TianYuan1024

Copy link
Copy Markdown
Contributor Author

Added the ## Linked Issues section (and its Chinese counterpart). There is no issue to close — this PR is a self-contained fix carved out of #9950, so the section references that PR without a closing keyword, per the template's "Otherwise reference without a closing keyword."

Also merged origin/main (a6d30ebc6b), which registers report_findings in the permission alias table and clears the two permission-manager.test.ts > resolveToolName exhaustiveness (#9827) failures this branch was inheriting from main.

Ready for re-run.

@TianYuan1024

Copy link
Copy Markdown
Contributor Author

Thanks for the stage-3 read — the approve-on-green marker was left at eb94b984df, and the head has since moved to 3a6f39ddb9, so it needs re-running rather than waiting.

What moved it, and why it was necessary: the only new commit is a merge of origin/main at a6d30ebc6b, which registers report_findings in the permission alias table. That commit fixes the two permission-manager.test.ts > resolveToolName exhaustiveness (#9827) failures this branch was inheriting from main — one of the red Test jobs the deferral was waiting on. Without the merge the marker could not have converted, because that job was red on main itself, not on this diff. No file in this PR's diff changed; git diff eb94b984df 3a6f39ddb9 -- packages/core/src/utils/ is empty.

The other red job you saw is the Windows lane, and it is not this diff either: daemon-git-worktree-guard{,.win32-lane}.test.ts plus review/* and Session.test.ts fail on 8.3 short paths (C:\Users\RUNNER~1 vs runneradmin) and on shell-quote stripping backslashes out of Windows paths. This branch touches no src/serve/ and no splitCommandsgit diff --name-only origin/main...HEAD is two files, both packages/core/src/utils/shellAstParser*. There is already a separate branch in flight for it (fix/windows-ci-temp-8dot3).

Note the run on the current head was cancelled about 40 seconds in, along with most other runs in the repository between 16:05Z and 16:49Z — a capacity event, not a failure on this commit.

@qwen-code /triage

@TianYuan1024

Copy link
Copy Markdown
Contributor Author

@qwen-code /triage

(The earlier request had the mention at the end of the comment rather than the first line, so it never matched the trigger — resending it correctly.)

The stage-3 read left approve-on-green sha=eb94b984df, and the head is now 3a6f39ddb9, so the marker is stale. The only change between them is a merge of origin/main at a6d30ebc6b, which registers report_findings in the permission alias table and clears the two permission-manager.test.ts > resolveToolName exhaustiveness (#9827) failures — one of the red jobs the deferral was waiting on. git diff eb94b984df 3a6f39ddb9 -- packages/core/src/utils/ is empty.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants