Skip to content

feat(core): bind PRs created via gh pr create in the session shell - #9739

Merged
wenshao merged 70 commits into
QwenLM:mainfrom
wenshao:feat/session-pr-gh-create
Sep 2, 2026
Merged

feat(core): bind PRs created via gh pr create in the session shell#9739
wenshao merged 70 commits into
QwenLM:mainfrom
wenshao:feat/session-pr-gh-create

Conversation

@wenshao

@wenshao wenshao commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Closes the last binding-source gap of the session↔PR feature: sessions whose PR was created by the agent running gh pr create in the shell (instead of the Web Shell Git dialog) now get bound too. Two complementary paths share one detector. Live: after a foreground shell command completes, the shell tool recognizes gh pr create (excluding --dry-run), extracts the PR URL that gh prints on success, and writes the session's PR sidecar directly with state open — the same tool-process-writes-sidecar pattern as worktree sessions, so it works in both CLI and daemon modes and shows up in the sidebar within the existing ~2s list refresh. Retroactive: the on-demand backfill route gains a third source that pairs each run_shell_command call with its response in persisted transcripts (by part id) and applies the same detector, recovering PRs created before the live hook existed. Failed or dry-run creates print no URL, which is the false-positive gate on both paths.

Why it's needed

Operators whose agents create PRs from the shell saw no PR badge on those sessions and could not search them by PR number — the original feature only bound GitDialog creations, and the branch-based backfill misses PRs whose head branch never appeared as a session git branch. The printed-URL source is authoritative for exactly this flow, live and historically.

Reviewer Test Plan

How to verify

  • Unit coverage: the detector (success URL, wrapped commands, gh.exe, non-create commands, dry-run/failure), the shell post-hook (mocked execution resolving with a PR URL writes the sidecar with state: 'open'; failure output writes nothing), and the backfill retroactive source (a transcript with a paired gh pr create call/response binds the printed URL even when gh is unavailable).
  • Live: in a daemon session, ask the agent to run a command whose output mimics a successful create (or run a real gh pr create against a scratch repo); the sidebar badge for that session appears within ~2s. Re-run POST /sessions/backfill-prs on a workspace with old transcripts containing gh pr create runs and confirm bound counts them.

Evidence (Before & After)

Before: a session that ran gh pr create carried no prs binding (badge absent, search by PR number missed it) unless its git branch happened to be a PR head. After: the binding is written at create time (live) or by backfill (historical), with the same badge/search/tooltip behavior as GitDialog-bound sessions.

Tested on

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

Environment

Repo vitest suites (core 33 + shell 305 + cli 27 targeted) plus build/typecheck/lint; macOS local.

Risk & Scope

  • Main risk or tradeoff: the detector is a heuristic on the command string, so a command merely containing gh pr create plus a PR-looking URL in output would bind; the URL requirement keeps this near-impossible in practice, and bindings are append-only/bounded (cap 10) with idempotent re-runs. The shell hook is best-effort and can never alter the tool result.
  • Not validated / out of scope: Windows/Linux not exercised locally (no OS-specific code paths); backgrounded shells are not scanned live (their output streams to files) — backfill covers historical ones.
  • Breaking changes / migration notes: none; purely additive sources writing the existing sidecar schema.
  • Stacked on feat(serve): backfill session PR bindings and refresh their merge state #9729 (backfill + state refresh): this branch includes those commits until feat(serve): backfill session PR bindings and refresh their merge state #9729 merges; the PR diff shrinks automatically afterwards.

Linked Issues

Follow-up to #9543 (session↔PR binding); stacked on #9729.

中文说明

这个 PR 做了什么

补齐会话↔PR 绑定的最后一个来源缺口:agent 在 shell 里 gh pr create 创建的 PR(而非 Web Shell GitDialog)现在也能绑定。两条互补路径共享一个检测器。实时:前台 shell 命令完成后,shell 工具识别 gh pr create(排除 --dry-run),提取 gh 成功时打印的 PR URL,直接以 state open 写会话 PR sidecar——与 worktree 会话相同的"工具进程直写 sidecar"模式,CLI/daemon 双模生效,约 2s 内在侧栏展示。回填:按需 backfill 路由新增第三来源,按 part id 配对 transcript 里 run_shell_command 的 call/response 并复用同一检测器,恢复 live hook 存在之前创建的 PR。失败/dry-run 不打印 URL,是两条路径天然的防误报闸门。

为什么需要

agent 从 shell 创建 PR 的操作者看不到这些会话的 PR badge,也无法按 PR 号搜索——原功能只绑 GitDialog 创建,基于分支的回填又漏掉 head 分支从未作为会话 gitBranch 出现的 PR。"创建时打印的 URL"对这一流程是权威来源,实时与存量皆然。

审查者测试计划

如何验证

  • 单测:检测器(成功 URL、包装命令、gh.exe、非 create 命令、dry-run/失败)、shell post-hook(mock 执行返回含 PR URL 的输出时以 state:'open' 写 sidecar;失败输出不写)、backfill 回填源(gh 不可用时也能按 transcript 里配对 call/response 的打印 URL 绑定)。
  • 真实验证:daemon 会话里让 agent 跑 gh pr create(或模拟其输出),约 2s 内出现 badge;对含历史 gh pr create 痕迹的 workspace 重跑 POST /sessions/backfill-prs,确认 bound 计数。

证据(前后对比)

之前:跑过 gh pr create 的会话无 prs 绑定(badge 缺、按号搜索漏),除非其 git 分支恰为 PR head。之后:创建时(实时)或 backfill(存量)写入绑定,badge/搜索/tooltip 行为与 GitDialog 绑定一致。

测试平台

macOS ✅;Windows/Linux ⚠️ 未本地验证(无 OS 特有路径)。

环境

仓库 vitest 套件(core 33 + shell 305 + cli 27 定向)+ build/typecheck/lint;macOS 本地。

风险与范围

  • 主要风险/权衡:检测器对命令字符串是启发式,若命令仅含 gh pr create 字样且输出恰有 PR 样式 URL 会误绑;URL 必要条件使其实践中几乎不可能,且绑定 append-only/有界(cap 10)、重跑幂等。shell hook best-effort,绝不改变工具结果。
  • 未验证/超出范围:Windows/Linux 未本地跑;后台 shell 不做实时扫描(输出流式入文件)——backfill 覆盖存量。
  • 破坏性变更/迁移:无;纯增量来源,写既有 sidecar schema。
  • 叠在 feat(serve): backfill session PR bindings and refresh their merge state #9729(backfill + state 刷新)之上:feat(serve): backfill session PR bindings and refresh their merge state #9729 合入前本分支包含其提交,合入后 PR diff 自动收缩。

关联

#9543(会话↔PR 绑定)后续;叠在 #9729 之上。

设计更新(最终形态,覆盖上文旧描述)

经多轮 review 迭代,绑定来源与闸门已重构,上文"检测器启发式/转录 call-response 配对"等描述作废:

  • 实时绑定:shell post-hook 以 gh 本身为归因权威——命令须通过执行闸门(仅作"是否执行了 gh pr create"的判断),且 gh 事后解析出该分支的 OPEN PR、命令输出携带该 URL、pre-run 快照证明该 PR 非运行前已存在、仓库身份(自身或已确认的 fork 父仓库)匹配。仅凭命令/输出文本不能伪造绑定。
  • 存量回填:来源仅两个——用户输入的 /review <N|#N|url> 命令(优先读 slash_command 原始命令记录)与 worktree pr-<N> 约定(slug 已保留该命名空间)。转录中的 gh pr create 痕迹与 session git 分支两个来源已移除(前者无法归因、存在伪造向量;后者实测纯噪声)。
  • 接受的已知限制:执行闸门对引号/表达式盲(quote-aware tokenizer 在 tools/shell.ts,不能进入 serve 快速路径闭包),残余过匹配因 gh 归因闸门而无害;纯后台 shell 的 create 不实时绑定、也不从转录恢复(/review 与 worktree 约定可覆盖);backfill 为按需管理路由,整读转录。

2026-08-29 接手更新(HEAD 0a3bfca

  • 与 main 合并:解决与 feat(serve): make Aone a first-class platform for session PR bindings #10287(Aone 一等平台)在 session-pr-backfill 上的结构性冲突——Aone 沿用本 PR 的来源模型(/review <N|#N|url> + worktree pr-<N> 约定,任何平台都不用 transcript 分支映射),约定号与 /review N 经有上限、带缓存的 a1 repo mr view 解析、绝不从 remote 拼 URL,Aone 化之前伪造的 <origin>/pull/<N> 绑定在计划里原地修复;注入的 AoneMrBackend seam 收为 view-only(mr list 不再在 backfill/refresh 的调用面上,exec 层原语保留)。响应字段:platformghAvailable 仅 GitHub。
  • 第 20 轮仍 standing 的 Critical 处置
    • 修复:R18-1(sidecar 锁按规范化路径取得、不再预先物化空文件,b6e750c247)、R19-2(/review <url> 过 sidecar 形状校验,replaceSessionPrs 在写边界拒绝读侧会拒绝的条目)、R19-3(planner 溢出裁剪按 provenance 排序,约定号保护成为同一规则而非特判,51acfb3c8c)、R20-7(未信任 gh 页仓库的 /review <url> 只绑自己命名的 PR,其 URL 不借给同号的裸号/约定号)、R20-15(upsertSessionPr 与批量写入同样按 provenance 封顶,bridge reconcile 测试改为真实分歧)、R20-16(跨 URL 重绑只盖 candidate 的 source)、R14-1 第三半(--worktree pr-42 对已注册的 PR-backed worktree 按字面 slug 重新附着,不存在时拒绝并提示 --worktree=#4222dbcf533c);另修 core normalizeRemoteToWebUrl 丢 https 端口的问题并删除 backfill 的孤儿副本(c332e99872)。
    • 按设计声明(已写入 docs/design/2026-08-20-webshell-session-pr-binding.md 范围边界,f0eba2cb34):R13-2(promote 成功 / is_backgroundgh pr create 不实时绑定,经 /review <N> 或约定绑定;docstring + 两条负向测试钉住)、R16-2(执行闸门语法收口为文档化的封闭集合——只做「任意路径限定的 gh」与「嵌套 wrapper 链」两处封闭式放宽,inline env 采集改为按命令顺序、建模 unset/env -u/env -i 删除侧、${VAR:-…} 保持字面量;语法之外 fail-closed,不再逐形状扩展)、R20-8(fork PR worktree 经 exit_worktree 删除受既有 hasUnmergedWorktreeCommits 守卫拒绝——合并基线即如此,本 PR 只放行 slug 形状,删除语义豁免属独立后续)。
  • 评审线程:102 条未决线程逐条回帖并 resolve——61 条为 bot 台账在后续轮次已撤下的历史发现(回帖引用撤下的轮次与该轮评审的 commit),其余按上文处置回帖。
  • 第 21 轮(对 c332e99):CI 全绿,唯一 Critical 为 R19-3 fix-induced——planner 对无 source 的重提占位者按本轮 stamp 排序,会让 provenance 记录之前的 GitDialog 绑定被新 review 挤出;已改为沿用 sidecar 阶梯自身的 rank(无 source 高于 review),新增用例钉住,五个封顶夹具改为显式 source:'review' 种子(198c733be2)。顺带清掉延后清单里的一行级项:单数 upsert 跨 URL 重绑丢显式 source、/review #N 正向测试、过时注释、bridgeClient 重复 import、设计文档三处与代码不符。
  • 第 22 轮(对 198c733):CI 全绿,2 条 Critical 都在 Aone × /review <url> 形态:Aone 上 /pull/N 形态只可能是历史伪造 URL,却被直接借出绕过 mr view(R22-1);两段仓库 key 折叠嵌套 group,兄弟项目的形态能过闸(R22-2)。修法:Aone 上 URL 形态只当号源、绝不借 URL(URL 唯一来源 mr view),且只承认恰为本仓库 remote 伪造形状的形态(全路径精确匹配);两条正向/负向用例钉住(166a639275)。
  • 第 23 轮(对 166a639):CI 全绿,1 条 Critical(R23-1,成立):planner 按 max(本轮 stamp, 持久化 source) 保护重提占位者,却把条目原样写回——先 /review 100 再获得 pr-100 关联的条目磁盘上仍是 review 级,其它按 authority 封顶的写入会最先挤掉它。修成 kept 原地提升 source(不动 url/createdAt/位置、绝不降级、只对通过同 PR 身份判定的 plan 成员),pre-provenance 约定占位者一次迁移写入后幂等;两条用例 + 变异验证(f3e41ac2e7)。
  • 第 24 轮(对 f3e41ac):1 条 Critical(R23-1 追加,成立):原地提升按 plan 成员资格盖章,而 GitHub 侧同 PR 判定在该号本轮解析不到时 fail-open,外仓同号占位者会被永久盖成 worktree。修成只在身份可证明时提升(gh 解析 URL 规范化相等 / 本 workspace <remote>/pull/N 形状 / Aone detailUrl 形状),裁剪的 fail-open 不动;负向用例 + 变异验证(d57f5ff593)。该轮 Test/Serve A/B 为 runner 超时 cancelled(无测试失败),本次推送重跑。
  • 2026-08-30 合并 main(522cb3a2af:解决与 fix(core): Preserve ownership during session cleanup #10300(会话清理所有权)在 sessionService 的冲突——保留 main 的 moveArchiveSidecars 助手与 assertCleanupOwned 命名,助手内 PR sidecar 一腿改走本分支的带锁 moveSessionPrSidecar(无锁私有 movePrSidecar 不复活);main 针对私有方法写的所有权用例改为断言 fence 传入带锁搬移、SessionWriterLostError 拒绝不被降级为 warning;split pair 合并语义仍由 session-pr-service.test.ts 钉住。合并后对 main 的净差异只剩本分支的三块。d57f5ff593 上 Test/Serve A/B/E2E 三个 job 均为 runner 超时 cancelled(无断言失败),本次 merge head 重跑。
  • 第 25 轮(对 522cb3a):1 条 Critical(R22-3,即早年推迟的 R18-2,成立):封顶分歧时 updateSessionMetadata 的事件先带着位置封顶的错列表发出,setSessionPrs 调和后既不发事件也不 bump revision。修成 setSessionPrs 中心化补发:成员真实变化时才发 corrective session_metadata_updated + markSessionCatalogChanged(),三个路由/dispatch 调用点与 backfill live-entry 同步一次覆盖,below-cap 场景零额外事件(975c2862de,变异验证)。同时 UpdateBranch 合入 38 个 main commit(含 ECS runner 事故修复 ci: allow full test jobs to finish on ECS runners #10558/test(ci): stabilize shared-runner budgets and cron interactive checks #10648,合并 CLEAN),merge head d8767e032f;bridge 882/882、server 1162/1162、transport/backfill/startup 479 全绿。
  • 第 26 轮(对 d8767e0):2 条 Critical 均成立并修复(b07933d075a9bc0e06d7,皆有变异验证):R26-27 mergeSessionPrLists 是最后一个能在同 PR 去重时降级 provenance 的写入方(split pair 合并把 create 降成 review 后被 authority cap 挤出),改为同规范化 URL 下保留更强 source、最新 createdAt 仍占槽;R26-1 URL 形态号被排在全部裸号之后、跨形态丢失转录年龄序,收集器改为按转录顺序发出统一 mention 流(首次出现占位、bare 性粘附),同级裁剪的位置 tie-break 恢复为真实年龄代理。
  • CI 定性(d8767e032f 三红):Test 的 6 处失败全是负载敏感计时用例/vitest worker RPC 抖动(voice hook 20s、process-env-guard 60s、refresh 清理 ENOTEMPTY 本地 3/3 过、hook 进程树 5s、recall 延迟 63ms>50、vscode bundle 5s);E2E smoke 用的是纯客户端 mockDaemon(serve 端代码不在回路),本地补装 @tanstack/react-table 后 3/3 全过,CI 端失败与 runner 池强相关——其它 PR 成功例全在 hk4/hk5 新池,本 PR 三次全被调度到 sg-5/sg-7/hk3 降级池。无分支缺陷;随本次推送重跑。
  • 第 27 轮(对 a9bc0e0):CI 全绿(Test 26m / Serve A/B 21m / E2E 6m41s——此前三红确系 runner 池事故,随修复自愈)。1 条 Critical(R27-1,成立):fork 布局下 attested() 两个析取支都够不到父仓库 URL(numberToUrl 按 fork key 闸门、remote 是 fork 形状),约定占位者永远无法被证明身份、提升永不落地。修法按建议加第三支——pageMapTrusted 门控下与受信页 URL 规范化相等;fork 正例 + divergent 负例 + 变异验证(0a3bfcaa65)。
  • 二次合 main(708efb3a65:解决与 feat(web-shell): derive session issue bindings from the closing references of bound PRs #10425(issue 快照)在整个 session-pr 面的冲突。并集:SessionPr 同时携带本分支的 source 与 main 的 issues(校验、同 PR 重绑保留快照、authority cap 共存);updateSessionPrStates 取 main 的 fetched 形态;bridge 投影走 toSessionPrInfo、校正事件的 unchanged 比较纳入 issues;metadata 路由保留本分支的 setSessionPrs 调和。mergeSummaryPrs 统一两模型:sidecar 供序与封顶门;规范化相等 → live 拼写 + sidecar state/issues;规范化不等(另一个 PR)→ sidecar 行整体获胜、快照永不跨挂;重号 sidecar 按 URL 匹配。两条旧测试按统一语义重钉,main 新增的 canonical-equal / 重号测试原样通过;server 全量 1170/1170、bridge 886/886、core 99、backfill 101。

Legacy sessions predate the PR-binding feature, so the sidebar had no way to answer 'which session produced PR N'. An on-demand route scans every trusted workspace's persisted sessions, resolves PR numbers from the worktree slug/branch convention and from transcript gitBranch x gh headRefName intersections (the dominant source in practice), and writes the existing .pr.json sidecars. Bound PRs now carry a state snapshot (open/merged/closed) that a 5-minute daemon sweep advances via a slim gh pr list --state all query, and the sidebar badge dims merged PRs while the tooltip names merged/closed ones.
run-qwen-serve is a pre-listen bundle root whose static closure must stay free of the SessionService chain (glob et al.). Loading session-pr-refresh statically pulled that chain in; a dynamic import() of the core barrel from inside the refresh module was worse — it made the barrel's full namespace live and poisoned the shared chunk for every static barrel importer (ACP agent included). Load the whole refresh module through a dynamic import at timer start instead, guarded by a generation counter against dispose races.
The shell tool now recognizes a completed gh pr create (excluding --dry-run) by the PR URL gh prints on success and writes the session's PR sidecar directly with state open, mirroring the worktree sidecar pattern (CLI and daemon modes alike). The backfill route gains the same detector as a third, retroactive source, pairing run_shell_command calls with their responses in persisted transcripts so sessions predating the live hook bind too.
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Re-run of triage on the current head (after the round-27 fix and the second main merge).

  • Template: complete ✓
  • Problem: a real gap in an existing feature, not theoretical hardening. The session↔PR binding feature (feat(web-shell): Bind GitHub PRs to sessions with sidebar badge and search #9543, feat(serve): backfill session PR bindings and refresh their merge state #9729) only bound PRs created through the Web Shell Git dialog; PRs an agent creates via gh pr create in the shell stayed unbound (no badge, no search by PR number), and the branch-based backfill could not reach them when the head branch never appeared as a session git branch. Observed, operator-visible gap.
  • Direction: aligned — this closes the last binding source of an established Web Shell feature rather than adding new surface. Author is a maintainer.
  • Size: core paths touched. ~2,400 production-logic lines vs ~5,700 test lines vs ~100 docs lines (plus 76 lockfile lines). feat type — no hard block applies; the two-tier core gate is exempt for maintainer-authored PRs. Large, but the line count is dominated by tests and by hardened edge cases accumulated over 27 review rounds, not scope creep.
  • Approach: the final design matches my independent proposal for this problem: a live shell post-hook that treats gh itself as the attribution authority (pre-run snapshot of branch PR/branch name/repo keys, post-run gh pr view, URL must appear in command output, repo and branch identity pinned, fail-closed on any unresolved state), plus a retroactive backfill limited to the two sources that carry attribution — user-typed /review <N|#N|url> commands and the worktree pr-<N> convention. The unattributable sources (transcript gh pr create traces, bare gitBranch) were removed — one as a forgery vector, one as measured noise. Nothing here reads like it could be cut 80%.
  • Risk: Stage 1e matches two high-risk paths — packages/core/src/tools/shell.ts and packages/cli/src/acp-integration/session/Session.ts. Full Stage 2 depth and CI evidence were applied accordingly (next comment).

Moving on to code review. 🔍

中文说明

对当前 head(第 27 轮修复与二次合并 main 之后)重跑 triage。

  • 模板:完整 ✓
  • 问题:既有功能的真实缺口,而非理论加固。会话↔PR 绑定(feat(web-shell): Bind GitHub PRs to sessions with sidebar badge and search #9543feat(serve): backfill session PR bindings and refresh their merge state #9729)此前只绑定 Web Shell Git 对话框创建的 PR;agent 在 shell 里 gh pr create 创建的 PR 不绑定(无 badge、无法按 PR 号搜索),且 head 分支从未作为会话 git 分支出现时基于分支的回填也覆盖不到。是观测到的、操作者可见的缺口。
  • 方向:对齐——补齐既有 Web Shell 特性的最后一个绑定来源,不是新增面。作者为维护者。
  • 规模:触及核心路径。生产逻辑约 2,400 行、测试约 5,700 行、文档约 100 行(另有 76 行 lockfile)。feat 类型——无硬阻断;维护者作者的 PR 豁免两级核心门禁。体量不小,但行数主要由测试与 27 轮评审累积的边界加固构成,并非范围蔓延。
  • 方案:最终设计与我对该问题的独立设想一致:实时 shell post-hook 以 gh 本身为归因权威(pre-run 快照分支 PR/分支名/仓库身份、运行后 gh pr view、URL 须出现在命令输出中、仓库与分支身份钉住、任何状态不可解析即不绑),回填只保留两个携带归因的来源——用户键入的 /review <N|#N|url> 命令与 worktree pr-<N> 约定。不可归因的来源(transcript 中的 gh pr create 痕迹、裸 gitBranch)已移除——前者是伪造向量,后者实测纯噪声。没有可以砍掉 80% 的部分。
  • 风险:Stage 1e 命中两条高风险路径——packages/core/src/tools/shell.tspackages/cli/src/acp-integration/session/Session.ts。已按完整 Stage 2 深度与 CI 证据处理(见下一条评论)。

进入代码审查。🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 22, 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 9220eea. 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 22, 2026

Copy link
Copy Markdown
Collaborator

Code review (full pass over the production diff)

No blockers found. This has been through 27 review rounds and it shows — the adversarial cases I looked for are already pinned by negative tests. What I verified in the current head:

  • Live binding gate (shell.ts + session-pr-service.ts): text matching is only an execution gate over a documented closed grammar; attribution is gh itself. Pre-run snapshot (branch PR, branch name, repo keys) is awaited before spawn; post-run the binding needs an OPEN PR on the same branch, a number different from the pre-run snapshot (rejects the gh pr create || gh pr view retry shape), repo identity matching the pre-run keys (self or confirmed fork parent), the gh URL present in output, and it fails closed on any errored/uncaptured pre-state. I chased the forgery vectors (echo-forged URLs, mid-run branch switch, mid-run origin retarget, dry-run, failed create, promoted/background runs) — each declines, and each has a test.
  • Sidecar writes: cross-process proper-lockfile plus the in-process queue, lock taken on the canonicalized parent path so an absent sidecar is never materialized; provenance-ranked cap (worktree > create > pre-provenance > review) applied consistently by every writer (upsertSessionPr, upsertSessionPrs, mergeSessionPrLists, backfill planner) so a reviewed-number accumulation can never evict the PR a session exists for; write boundary declines entries the reader would reject (whole-list poison protection).
  • Backfill: sources are only the two attributable ones — user-typed /review <N|#N|url> (command position only: first text part or slash_command rawCommand, so assistant prose, tool results and @-imported content cannot seed bindings) and the worktree pr-<N> convention. The gh page is repo-key gated with fail-closed on unknown workspace key and on divergent resolution; URL forms pass a repo gate plus the sidecar shape check before persistence; Aone never assembles URLs (capped mr view only) and repairs its legacy fabricated bindings.
  • Propagation: the child persists the sidecar itself and only notifies the daemon (qwen/notify/session/pr-binding, payload-validated in bridgeClient); setSessionPrs reconciles the live entry and republishes a corrective event only when the reconciled list actually diverges.

Two non-blocking observations:

  • bridgeClient.ts re-implements the binding-URL shape check (length, http(s), control characters) inline, while core now exports isValidSessionPrUrl with exactly that rule — and the file already imports SESSION_PR_URL_MAX_LENGTH from core. Worth reusing in a follow-up; not blocking.
  • The inline-credential overlay models env -u/unset/env -i as undefined entries handed to child env. If Node ever stringified those, the verification legs would fail to authenticate and the binding would be missed — fail-closed, never wrong. Documented behavior; no action needed.

The changed-files map and the live-binding flow:

sequenceDiagram
    participant P1 as Command
    participant P2 as ShellToolInvocation
    participant P3 as gh and git
    participant P4 as PR sidecar
    participant P5 as Bridge
    participant P6 as Web Shell client
    P2->>P3: pre-run snapshot - branch PR, branch name, repo keys
    P2->>P1: run the command
    P1-->>P2: exit 0 with output
    P2->>P3: post-run gh pr view for the branch
    P3-->>P2: open PR with url
    P2->>P2: gates - new number, repo and branch identity, url in output
    P2->>P4: upsertSessionPrs, source create, cross-process lock
    P2->>P5: pr-binding notification
    P5->>P6: catalog mark, client refetches in about 2s
Loading
Files changed (30 of 39 shown)
File What changed
packages/core/src/services/session-pr-service.ts provenance sources, authority cap, cross-process lock, execution gate, inline-credential collector, batch upsert, locked sidecar move
packages/core/src/services/session-pr-service.test.ts tests for the gate grammar, env collection, caps, locking, move and poison protection
packages/core/src/tools/shell.ts the gh-pr-create post-hook: pre-run snapshot, gh-attribution gates, best-effort sidecar write
packages/core/src/tools/shell.test.ts binding positives and every decline path (retry, forged URL, branch switch, fork layout, background)
packages/core/src/utils/github-prs.ts remote/repo-key normalization, branch PR snapshot, branch name, attribution repo keys
packages/core/src/utils/github-prs.test.ts tests for the normalization and snapshot helpers
packages/cli/src/serve/routes/session-pr-backfill.ts rewritten sources: /review commands and worktree convention, repo-gated gh page, provenance planner, Aone repair
packages/cli/src/serve/routes/session-pr-backfill.test.ts forgery vectors, cap idempotency, fork layouts, traversal guard, archive transitions
packages/core/src/services/sessionService.ts locked moveSessionPrSidecar replaces the unlocked private move, pr-bound callback, deterministic id sweep
packages/core/src/services/sessionService.test.ts move-failure warning paths for archive and unarchive
packages/cli/src/serve/server/session-list.ts summary merge driven by sidecar binding-time order, live-only entries gated on the cap
packages/cli/src/serve/server.test.ts merge semantics, reconcile calls, metadata route stamping
packages/acp-bridge/src/bridge.ts setSessionPrs reconciles and republishes a corrective event on divergence
packages/acp-bridge/src/bridge.test.ts reconcile and republish tests
packages/acp-bridge/src/bridgeClient.ts validates the child pr-binding notification and marks the catalog
packages/acp-bridge/src/bridgeTypes.ts setSessionPrs contract doc update
packages/cli/src/acp-integration/session/Session.ts registers and clears the pr-bound callback
packages/cli/src/serve/acp-http/dispatch.ts stamps source create on GitDialog binds, reconciles the live entry
packages/cli/src/serve/acp-http/transport.test.ts notification and reconcile coverage
packages/cli/src/serve/routes/session.ts metadata routes stamp source create and reconcile
packages/core/src/config/config.ts relocateWorkingDirectory carries the pr-bound callback to the fresh service
packages/core/src/config/config.test.ts callback carry-over test
packages/core/src/services/gitWorktreeService.ts reserves the pr-N slug shape, prBacked creation flag
packages/core/src/services/gitWorktreeService.test.ts slug reservation tests
packages/cli/src/startup/worktreeStartup.ts re-attach a literal pr-N slug, reject creation with a pointer to the #N form
packages/cli/src/startup/worktreeStartup.test.ts re-attach and reject tests
packages/core/src/tools/exit-worktree.ts allows exiting existing PR-backed pr-N worktrees
packages/cli/src/serve/server/aone-mrs.ts backend seam shrinks to view-only, list drops off the backfill path
docs/design/2026-08-20-webshell-session-pr-binding.md design doc rewritten to the final source model and scope boundaries
packages/core/src/tools/exit-worktree.test.ts reserved-shape acceptance test
…and 9 more files second design doc, package-lock, and test-only stubs for Session/AppContainer/refresh/aone-mrs/fast-path lanes

Testing — the PR's own CI on the reviewed commit

All four pull_request-event runs completed green: Qwen Code CI (includes Test (ubuntu-latest, Node 22.x)), Serve A/B (ubuntu-latest, Node 22.x), Security Checks, and SDK Java. The only red check on the commit is review-pr, and it is bot infrastructure, not the PR's code: it belongs to the 🧐 Qwen Pull Request Review workflow (pull_request_target event), whose own fallback comment records that the review agent timed out at the 21600s budget cap, and the job log tail shows only post-job credential cleanup — no test failure. The PR's own suite: ~5,700 new/changed test lines, all running in that green Test job. Not verified here: a live end-to-end run (unattended runs never execute PR code).

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Serve A/B (ubuntu-latest, Node 22.x) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) success
Real daemon E2E / Java 11 success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
Dependency CVE audit success
Secret scan (TruffleHog) success
Classify PR success
precheck-pr / precheck success
Java builds (ubuntu 11/17/21, macos 21, windows 21) success
label / assign / authorize / fallback-comment success
Remind on force-push success
review-pr failure — bot review timeout, not PR-caused (see prose above)
Test (macos/windows), Integration Tests (CLI, No Sandbox), Post Coverage Comment skipped (scheduled/conditional lanes)

Sandboxed verification is already settling the one claim CI cannot: the end-to-end live flow (a real gh pr create in a session → sidecar → badge) and retroactive backfill over real transcripts are not provable from the diff or a mocked unit suite — an A/B verification run was triggered on exactly this commit by this re-run (run 33565757528); its report posts here when it completes.

中文说明

代码审查(生产代码 diff 全量过了一遍)

未发现阻断项。这个 PR 经历了 27 轮评审,效果显而易见——我去找的攻击面大多已有负向测试钉住。在当前 head 上核实的内容:

  • 实时绑定闸门(shell.ts + session-pr-service.ts:文本匹配只是文档化封闭语法上的执行闸门,归因以 gh 本身为准。pre-run 快照(分支 PR、分支名、仓库身份)在 spawn 前完成等待;运行后绑定要求同分支存在 OPEN PR、PR 号与 pre-run 快照不同(拒绝 gh pr create || gh pr view 重试形态)、仓库身份与 pre-run 键一致(自身或已确认的 fork 父仓库)、命令输出包含 gh 的 URL,且任何 pre-state 出错/未采集即不绑。逐一追查了伪造向量(echo 伪造 URL、运行中切换分支、运行中改 origin、dry-run、失败创建、转后台/后台运行)——全部拒绝且各有测试。
  • sidecar 写入:跨进程 proper-lockfile + 进程内队列,锁按规范化父目录路径取得、不预先物化空文件;按 provenance 排序的封顶(worktree > create > 无 provenance > review)在所有写入方(upsertSessionPrupsertSessionPrsmergeSessionPrLists、backfill planner)一致执行,评审号的累积永远挤不掉会话赖以存在的 PR;写边界拒绝读侧会拒绝的条目(整表投毒防护)。
  • 回填:来源只剩两个可归因的——用户键入的 /review <N|#N|url>(仅命令位置:首个 text part 或 slash_command rawCommand,assistant 散文、工具结果、@导入内容无法种入绑定)与 worktree pr-<N> 约定。gh 页按仓库 key 闸门,workspace key 不可解析或 gh 解析发散时 fail-closed;URL 形态须过仓库闸门与 sidecar 形状校验才可持久化;Aone 绝不拼 URL(仅有上限的 mr view)并原地修复历史伪造绑定。
  • 传播:子进程自己持久化 sidecar,只通知 daemon(qwen/notify/session/pr-bindingbridgeClient 校验载荷);setSessionPrs 调和 live 条目,仅在调和后列表真实分歧时补发校正事件。

两条非阻断观察:

  • bridgeClient.ts 内联重新实现了绑定 URL 形状校验(长度、http(s)、控制字符),而 core 现在导出的 isValidSessionPrUrl 正是同一规则——该文件也已从 core 引入 SESSION_PR_URL_MAX_LENGTH。建议后续复用;不阻断。
  • inline 凭据 overlay 用 undefined 条目建模 env -u/unset/env -i。即便 Node 将其字符串化,验证腿也只会因鉴权失败而错过绑定——fail-closed,不会误绑。文档化行为,无需处理。

测试——被审提交上的 PR 自身 CI

四个 pull_request 事件运行全部绿:Qwen Code CI(含 Test (ubuntu-latest, Node 22.x))、Serve A/B (ubuntu-latest, Node 22.x)Security ChecksSDK Java。该提交上唯一红检是 review-pr,属 bot 基建而非 PR 代码:它属于 🧐 Qwen Pull Request Review 工作流(pull_request_target 事件),其 fallback 评论记录评审 agent 在 21600 秒预算上限超时,任务日志末尾只有收尾的凭据清理,无任何测试失败。PR 自身测试套件:约 5,700 行新增/改动测试,均跑在上述绿色 Test 任务中。此处未验证:真实端到端运行(无人值守运行从不执行 PR 代码)。

沙箱验证正在落定 CI 无法覆盖的那一项主张:端到端实时流程(真实 gh pr create → sidecar → badge)与针对真实转录的回填无法从 diff 或 mock 单测证明——本次重跑已在同一提交上触发 A/B 验证运行(run 33565757528),完成后报告会贴在这里。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean across every stage; the reservations are non-blocking (one small reuse nit, and the end-to-end live flow rests on the sandboxed verification still in flight plus the author's macOS testing, not this review).

Stepping back: this PR started as "detect gh pr create output" and the review process correctly beat it into something much harder to fool — gh itself is the attribution authority, text matching is demoted to an execution gate over a closed grammar, and every writer caps by provenance so the PR a session exists for can never be evicted by reviewed-number churn. My independent proposal for this problem was essentially the final design minus a fraction of the adversarial cases, so there is no simpler path I'm aware of that was missed. The code is dense, but the density is in the problem, not in the prose: every gate has a named threat, every decline path a test, and the design doc keeps the scope boundaries honest (including what it deliberately does not cover).

The standing CHANGES_REQUESTED state comes from the earlier automatic review rounds against older commits; each round's findings are recorded as addressed in the PR's update log, and my fresh full pass over the current head found no blocker to add. The only red CI check is the bot review's own budget timeout, not the PR's suite.

Approving, pinned to the reviewed commit. The sandboxed A/B verification report will follow on this thread when it lands — worth a read before merge, but from where I sit this is ready.

中文说明

置信度:4/5 —— 各阶段均干净;保留意见都是非阻断的(一条小的复用建议,以及端到端实时流程依赖仍在进行的沙箱验证与作者的 macOS 自测,而非本次评审)。

退一步看:这个 PR 从"检测 gh pr create 输出"起步,被评审过程正确地打磨成了更难被欺骗的形态——以 gh 本身为归因权威,文本匹配降级为封闭语法上的执行闸门,所有写入方按 provenance 封顶,会话赖以存在的 PR 永远不会被评审号累积挤出。我对该问题的独立设想基本就是最终设计减去一小部分对抗情形,因此不存在被错过的更简路径。代码密度高,但密度来自问题本身而非行文:每道闸门都有明确的威胁,每条拒绝路径都有测试,设计文档也如实钉住了范围边界(包括明确不做的部分)。

当前挂着的 CHANGES_REQUESTED 来自早前针对旧提交的自动评审轮次;每轮发现均按 PR 更新日志记录为已处理,我对当前 head 的全新全量审查没有新增阻断项。唯一红检是 bot 评审自身的预算超时,不是 PR 的测试套件。

予以批准,钉在所审提交上。沙箱 A/B 验证报告会随后贴在本帖——合并前值得一读,但就我目前所见,这个 PR 已经就绪。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 0a3bfca, 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

process-env-guard scans serve sources for process.env reads; register QWEN_SESSION_PR_REFRESH_MINUTES as a documented process-scoped switch. GitDialog now sends state 'open' with the binding, so the dialog tests assert it.
A search command whose arguments mention 'gh pr create' (e.g. grep) paired with a PR-looking URL in its output would bind a bogus entry; require the phrase to start a command segment (env-prefixed and piped forms still count). Summarized output also elides owner/repo (github.com/.../pull/N) — such URLs are not usable link targets and are rejected.
@wenshao

wenshao commented Aug 22, 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 22, 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. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@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.

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

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

中文说明

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

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

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

Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/core/src/services/session-pr-service.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/server/session-pr-refresh.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session.ts
Comment thread packages/cli/src/serve/routes/session-pr-backfill.test.ts
@qwen-code-dev-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ AutoFix round 6 ended without publishing a reportview run.

中文说明

⚠️ AutoFix 第 6 轮结束但未发布报告 —— 查看运行

Review round 2 for the session PR binding feature:

- Bind the LAST printed /pull/N URL instead of the first, and require it
  to belong to the workspace repository (host/owner/repo vs the origin
  remote); the live shell hook additionally requires exit code 0 and
  skips when the remote cannot be resolved. Closes forged/foreign URL
  persistence via compound commands.
- Backfill inserts bindings in ascending authority (branch-mapped first,
  gh-pr-create evidence next, worktree convention last) so the session's
  own PR survives the tail-10 cap, and maps a shared head branch to the
  newest PR (slim query now requests updatedAt; first-wins mapping).
- Reject pr-0 and leading-zero worktree slugs — number 0 poisoned the
  whole sidecar read.
- Refresh sweep only stamps states onto bindings whose URL belongs to
  the queried repository, and counts entries actually rewritten.
- Remote resolution is async with a bounded timeout and attempted once
  per run; normalizeRemoteToWebUrl moved to core, drops ssh:// ports,
  and accepts any scp-style user.
@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 轮)。改动内容与我反驳保留之处如下:

Review round summary — PR #9739 (commit bd968f2b23)

This round implemented all five bounded Critical findings (R1-2 … R1-6) plus the three Suggestions entangled with them (R1-11, R1-19, R1-20), each with a regression test verified to fail on the pre-round code (mutation probes below). Per the ~8-findings-per-round budget, the remaining findings are deferred to the next round with explicit replies on their threads (comment-replies.json).

Resolved in code

Finding Disposition Change
[Critical] R1-2 (rc:3836860789) — detector binds the FIRST /pull/N URL anywhere, no exit-code gate, no attribution Fixed detectGhPrCreateBinding now binds the LAST matching URL and, when given a repo key, only URLs whose host/owner/repo matches it. Live shell hook additionally requires result.exitCode === 0 and resolves the workspace origin remote (fetchRemoteWebUrl) — an unresolvable remote binds nothing. Backfill applies the same repo gate to transcript-recovered URLs.
[Critical] R1-3 (rc:3836860792) — insertion order evicts the most authoritative binding first Fixed Backfill now inserts in ascending authority: branch-mapped → gh pr create evidence → worktree slug/branch convention last, so the session's own PR survives the tail-10 cap.
[Critical] R1-4 (rc:3836860793) — slim field set breaks newest-first ordering; last-write branch map resolves to the oldest PR Fixed Both halves: updatedAt added to GH_PR_LIST_FIELDS_SLIM (restores the newest-first sort) and branchToNumber is now first-wins, so a shared head branch maps to the newest PR.
[Critical] R1-5 (rc:3836860795) — pr-0 slug binds number 0, poisoning the whole sidecar read Fixed Both patterns are now [1-9]\d{0,8} (mirrors parsePRReference's n > 0 invariant; leading zeros rejected too). A pr-0 worktree session binds nothing.
[Critical] R1-6 (rc:3836860796) — sweep stamps states by bare number across repositories Fixed The sweep resolves the workspace repo key and only stamps bindings whose URL belongs to it; foreign-repo bindings are skipped, and an unresolvable remote updates nothing (fail closed, no gh call).
[Suggestion] R1-11 (rc:3836860805) — blocking execSync remote lookup, retried per candidate Fixed Remote resolution moved to core fetchRemoteWebUrl (async execFile, 5s timeout) and cached by attempt — one lookup per backfill run even when it fails.
[Suggestion] R1-19 (rc:3836860817) — ssh:// port kept in badge links; non-git@ scp remotes rejected Fixed normalizeRemoteToWebUrl (moved to core) reassembles with url.hostname (port dropped) and accepts any [user@]host:path scp-style remote. Regression tests added for ssh://git@host:2222/o/r.git and jdoe@host:o/r.git.
[Suggestion] R1-20 (rc:3836860818) — updated counts gh-confirmed numbers, not rewrites Fixed updateSessionPrStates returns the count of entries actually rewritten; the sweep accumulates it. Two-binding test asserts updated: 1 when gh confirms one change.

The design doc section describing the shell post-hook was updated to match the hardened semantics.

Deferred to the next round (explicit thread replies posted)

  • [Critical] R1-1 (rc:3836860788) — live binding never notifies the bridge, and [Critical] R1-7 (rc:3836860798) — cross-process sidecar writer race. These two share one root-cause fix and are intentionally tackled together: route the child-detected binding over the existing qwen/notify/* extNotification side-channel (title-update precedent) into a daemon-side handler that performs the GitDialog sequence (seedSessionPrsbridge.updateSessionMetadataupsertSessionPr), making the daemon the sole serialized writer and bumping the catalog revision. This spans core (sink on Config), cli (ACP Session wiring), and acp-bridge (demux + handler) and exceeds this round's budget; the fallback for R1-7 alone is a two-tier proper-lockfile guard (mailbox.ts convention; dependency already present).
  • Suggestions R1-8, R1-9, R1-10, R1-12, R1-13, R1-14, R1-15, R1-16, R1-17, R1-18, R1-21, R1-22, R1-23 — deferred per the per-round batch bound; each has a reply recording the deferral. R1-23's surviving-mutant list (alreadyBound read-back, out-of-alignment transcript pairing, effectiveEnv fixture, toHaveBeenCalledWith arg pins, untrusted-primary fixture) is the test plan for those fixes.
  • rv:5000918652 (review body) — its actionable content is the inline findings above; the disclosed gaps (integration suite not run locally, reverse-audit cap) are process notes, addressed here by the focused verification below.
  • ic:5381304346 (web-shell visual preview) — "one or more scenarios failed to render" against the CI mock daemon; no code-level evidence of a defect was available (linked artifacts are CI-side). The badge/merged-state wiring was independently audited and is covered by SessionPrBadge.test.tsx. If the preview keeps failing on the next head, its workflow artifacts are the place to look.
  • ic:5381345419 (serve A/B) — passed, no action.

Verification

Commands actually run this round (post-fix, at commit bd968f2b23):

  • npm run buildpassed (also re-run after formatting; 0 errors)
  • npm run typecheckpassed (0 TS errors)
  • npm run lintpassed
  • npx prettier --check on all 10 changed code files — passed (after --write on 5)
  • cd packages/core && npx vitest run src/services/session-pr-service.test.ts src/utils/github-prs.test.ts src/tools/shell.test.ts384 passed (incl. 5 shell binding tests, 10 detector tests, 20 new helper tests)
  • cd packages/core && npx vitest run src/services/sessionService.test.ts src/utils/atomicFileWrite.test.ts (adjacent suites) — passed
  • cd packages/cli && npx vitest run src/serve/routes/session-pr-backfill.test.ts src/serve/server/session-pr-refresh.test.ts src/serve/server.test.ts src/serve/process-env-guard.test.ts1112 passed (20 backfill + 11 refresh tests incl. 8 new)
  • cd packages/web-shell && npx vitest run client/components/SessionPrBadge.test.tsx client/components/dialogs/GitDialog.test.tsx18 passed

Mutation probes (each fix's witness verified red on pre-round code, then restored green):

  • R1-2 detector reverted to first-match/no-key → 3 detector tests fail
  • R1-2 exit-code gate removed → "does not bind a non-zero exit even when the output carries a URL" fails
  • R1-2 backfill repo gate removed → "does not recover a transcript URL from another repository" fails
  • R1-3 order reverted → "keeps the convention binding when weaker numbers overflow the cap" fails
  • R1-4 last-wins mapping restored → "maps a shared head branch to the newest PR" fails
  • R1-5 patterns reverted to \d{1,9}both zero/leading-zero tests fail
  • R1-6 scoping removed → "never stamps a same-number PR of another repository" fails
  • R1-11 attempt cache removed → "resolves the git remote at most once per backfill run" fails
  • R1-19 normalization reverted (port kept, git@-only) → both new remote-shape tests fail
  • R1-20 states.size counting restored → "counts only bindings whose state was actually rewritten" fails

Integration tests were not run: every changed behavior is exercised by the focused Vitest suites above (route tests use supertest; the sweep/backfill are tested through their public functions), not only through the bundled CLI.

中文说明

审查轮次总结 — PR #9739(提交 bd968f2b23

本轮实现了全部五个有界的 Critical 发现(R1-2 … R1-6),以及与它们纠缠在一起的三个 Suggestion(R1-11、R1-19、R1-20)。每一项都附带回归测试,并已验证该测试在轮次前代码上会失败(见下方变异探针)。按照每轮约 8 个发现的上限,其余发现推迟到下一轮,并在各自线程中留下了明确的回复(comment-replies.json)。

已在代码中解决

发现 处置 变更
[Critical] R1-2(rc:3836860789)——检测器绑定输出中任意位置的第一个 /pull/N URL,无退出码闸门、无归属判定 已修复 detectGhPrCreateBinding 现在绑定最后一个匹配 URL,且在给定仓库 key 时只接受 host/owner/repo 与之匹配的 URL。实时 shell hook 额外要求 result.exitCode === 0,并解析工作区 origin remote(fetchRemoteWebUrl)——remote 无法解析时不做任何绑定。回填对 transcript 恢复出的 URL 应用同样的仓库闸门。
[Critical] R1-3(rc:3836860792)——插入顺序导致最权威的绑定最先被挤出 已修复 回填现在按权威性升序插入:分支映射 → gh pr create 证据 → worktree slug/分支约定最后,确保会话自身的 PR 在尾部 10 个上限下幸存。
[Critical] R1-4(rc:3836860793)——slim 字段集破坏"最新优先"排序;后写覆盖的分支映射解析到最老 PR 已修复 两步都做:GH_PR_LIST_FIELDS_SLIM 增加 updatedAt(恢复最新优先排序), branchToNumber 改为先到优先,使共享 head 分支映射到最新 PR。
[Critical] R1-5(rc:3836860795)——pr-0 slug 绑定数字 0,毒害整个 sidecar 读取 已修复 两个模式均改为 [1-9]\d{0,8}(对齐 parsePRReference 的 n > 0 不变量;同时拒绝前导零)。pr-0 worktree 会话不再产生任何绑定。
[Critical] R1-6(rc:3836860796)——扫描按裸 PR 号跨仓库盖状态 已修复 扫描解析工作区仓库 key,只对 URL 属于该仓库的绑定盖状态;外部仓库绑定直接跳过;remote 无法解析时本轮不更新任何内容(失败即关闭,不发 gh 调用)。
[Suggestion] R1-11(rc:3836860805)——阻塞式 execSync remote 查询,且按候选重试 已修复 remote 解析移入 core fetchRemoteWebUrl(异步 execFile,5 秒超时),并按尝试缓存——即使失败,每次回填运行也只查询一次。
[Suggestion] R1-19(rc:3836860817)——ssh:// 端口保留进 badge 链接;非 git@ 的 scp remote 被拒绝 已修复 normalizeRemoteToWebUrl(移入 core)用 url.hostname 重组(丢弃端口),并接受任意 [user@]host:path scp 风格 remote。为 ssh://git@host:2222/o/r.gitjdoe@host:o/r.git 补充回归测试。
[Suggestion] R1-20(rc:3836860818)——updated 计的是 gh 页面确认过的数字,而非实际重写数 已修复 updateSessionPrStates 返回实际被重写的条目数;扫描累加该值。双绑定测试断言 gh 恰好确认一条变化时 updated: 1

设计文档中描述 shell post-hook 的章节已同步更新为加固后的语义。

推迟到下一轮(线程中已有明确回复)

  • [Critical] R1-1(rc:3836860788)——实时绑定从不通知 bridge;[Critical] R1-7(rc:3836860798)——sidecar 跨进程写竞态。这两者共享同一个根因修复,有意放在一起处理:把子进程检测到的绑定经现有 qwen/notify/* extNotification 侧信道(title-update 先例)路由到 daemon 侧处理器,执行 GitDialog 的完整序列(seedSessionPrsbridge.updateSessionMetadataupsertSessionPr),让 daemon 成为唯一的串行写入方并 bump catalog revision。该方案横跨 core(Config 上的 sink)、cli(ACP Session 接线)、acp-bridge(demux + 处理器),超出本轮预算;R1-7 单独的兜底方案是两层 proper-lockfile 守卫(mailbox.ts 约定;依赖已存在)。
  • Suggestion 类 R1-8、R1-9、R1-10、R1-12、R1-13、R1-14、R1-15、R1-16、R1-17、R1-18、R1-21、R1-22、R1-23——按每轮批量上限推迟;每项都有记录推迟原因的回复。R1-23 的存活变异体清单(alreadyBound 回读、错位配对的 transcript、effectiveEnv fixture、toHaveBeenCalledWith 参数钉死、不可信 primary fixture)就是那些修复的测试计划。
  • rv:5000918652(审查正文)——其可执行内容即上述 inline 发现;披露的缺口(集成套件未本地运行、反向审计达到轮数上限)属于流程说明,已由下方的聚焦验证覆盖。
  • ic:5381304346(web-shell 可视化预览)——"一个或多个场景渲染失败"(CI mock daemon 环境);没有可用的代码级缺陷证据(链接产物在 CI 侧)。badge/merged 状态接线已独立审计,且由 SessionPrBadge.test.tsx 覆盖。若下个 head 预览仍失败,应查看其工作流产物。
  • ic:5381345419(serve A/B)——通过,无需处理。

验证

本轮实际执行的命令(修复后,提交 bd968f2b23):

  • npm run build通过(格式化后再次执行;0 错误)
  • npm run typecheck通过(0 个 TS 错误)
  • npm run lint通过
  • 对全部 10 个改动的代码文件执行 npx prettier --check通过(其中 5 个先 --write
  • cd packages/core && npx vitest run src/services/session-pr-service.test.ts src/utils/github-prs.test.ts src/tools/shell.test.ts384 通过(含 5 个 shell 绑定测试、10 个检测器测试、20 个新 helper 测试)
  • cd packages/core && npx vitest run src/services/sessionService.test.ts src/utils/atomicFileWrite.test.ts(相邻套件)— 通过
  • cd packages/cli && npx vitest run src/serve/routes/session-pr-backfill.test.ts src/serve/server/session-pr-refresh.test.ts src/serve/server.test.ts src/serve/process-env-guard.test.ts1112 通过(20 个回填 + 11 个刷新测试,含 8 个新增)
  • cd packages/web-shell && npx vitest run client/components/SessionPrBadge.test.tsx client/components/dialogs/GitDialog.test.tsx18 通过

变异探针(每个修复的见证测试均已验证:轮次前代码上为红,恢复后为绿):

  • R1-2 检测器回退为首个匹配/无 key → 3 个检测器测试失败
  • 移除 R1-2 退出码闸门 → "退出码非 0 即使输出带 URL 也不绑定"失败
  • 移除 R1-2 回填仓库闸门 → "不恢复来自其他仓库的 transcript URL"失败
  • R1-3 顺序回退 → "弱编号溢出上限时保留约定绑定"失败
  • R1-4 恢复后写覆盖映射 → "共享 head 分支映射到最新 PR"失败
  • R1-5 模式回退为 \d{1,9}两个 zero/前导零测试失败
  • 移除 R1-6 仓库限定 → "不给其他仓库的同号码 PR 盖状态"失败
  • 移除 R1-11 尝试缓存 → "每次回填运行至多解析一次 git remote"失败
  • R1-19 归一化回退(保留端口、仅 git@)→ 两个新 remote 形态测试失败
  • R1-20 恢复 states.size 计数 → "只统计状态实际被重写的绑定"失败

未运行集成测试:所有变更行为均由上述聚焦 Vitest 套件覆盖(路由测试使用 supertest;扫描/回填经公共函数测试),并非只能通过打包后的 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 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.

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

  • timer trust-skip/reentrancy/dispose untested (f7) — named in R1-23 (re-posted on the PR)
  • core state validation clause negative test missing (f8) — named in R1-23 (re-posted on the PR)
  • draft→open guards untested in both consumers (f9) — named in R1-23 (re-posted on the PR)
  • !result.aborted conjunct unexercised (f10) — named in R1-23 (re-posted on the PR)
  • backfill gh call-arguments unasserted (g5) — named in R1-23 item (4) (re-posted on the PR)
  • sweep archived-state branch untested (g7) — named in R1-23 (re-posted on the PR)

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

Not reviewed: reverse audit — stopped at the reverse-audit round cap of 5 without converging.

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

  • packages/core/src/tools/shell.ts:3076 — [review] Remote resolved before the cheap detection gate — every exit-0 command pays a git walk + spawn
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:653 — [review] Route aggregation test asserts neither the trusted entry nor non-zero reduce totals
  • packages/cli/src/serve/run-qwen-serve.ts:5164 — [probe] Sweep-starting dynamic import has no .catch — uncaught exception (daemon crash) on the serve fast path
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:66 — [probe] pr() helper hardcodes state:'open' — merged/closed snapshot write path unpinned (flattening mutant 20/20 green)
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:179 — [probe] All-merged fast-path test cannot pin early-return-before-remote-resolution (hoisting mutant 11/11 green)
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:211 — [probe] No malformed-sidecar resilience test although R2-2's poisoning chain makes invalid sidecars reachable
  • packages/cli/src/serve/routes/session-pr-backfill.ts:142 — [probe] Backfill runs the detector unkeyed — a trailing foreign URL after gh's own loses a binding the live keyed path would make
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:59 — [probe] Negative-minutes guard untested — deleting it keeps the suite green and yields the 1 ms loop shape
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:160 — [probe] gh call count unpinned — a per-session-call mutant keeps 11/11 green (probe: committed 1 call vs mutant 2)
  • docs/design/2026-08-20-webshell-session-pr-binding.md:65 — [review] Design doc contradicts itself and the code on the slim field set (says three fields; constant has five)
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:181 — [probe] Multi-target update loop never exercised past the first entry — break-after-first mutant 11/11 green
  • packages/core/src/services/session-pr-service.ts:163 — [probe] --dry-run substring gate suppresses a real create when the flag appears in a quoted title/body or a later segment
中文说明

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

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:reverse audit — stopped at the reverse-audit round cap of 5 without converging。

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

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

Comment thread packages/core/src/services/session-pr-service.ts Outdated
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
Comment thread packages/cli/src/serve/routes/session-pr-backfill.test.ts
@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 轮)。改动内容与我反驳保留之处如下:

Autofix round summary — PR #9739 (review round 3)

Growth audit

Verdict: drift (growth-audit.json). KISS axis failed on the accumulated per-corner text gates around the gh pr create detector (exit-code gate, last-URL-wins, origin repo key) — R2-1's reproduced entrances prove no text-gate composition can attribute a printed URL to gh's own execution. The named simpler alternative (make gh the attribution authority; shrink text matching to an execution gate) was implemented FIRST this round, and it is also the R2-1 fix. Minimal-change axis passed: every hunk traces to the PR's feature or an accepted finding. The round stayed subtractive where possible: the heuristic detector, the transcript-URL backfill source, and the per-corner gates were deleted rather than extended.

Feedback dispositions

Finding Severity Disposition
R2-1 (rc:3837318249) Critical Fixed — structural attribution
R2-2 (rc:3837318251) Critical Fixed — write-time validation
R2-3 (rc:3837318255) Critical Fixed — convention reposition
R2-4 (rc:3837318256) Critical Resolved by design — raw map deleted
R2-5 (rc:3837318258) Critical Fixed — gh-page repo-key gate
R1-1 (rc:3837318261) Critical Fixedpr-binding notify → catalog mark
R1-7 (rc:3837318262) Critical Fixed — cross-process sidecar lock
R2-6…R2-9, R1-8, R1-10, R1-12…R1-16, R1-21, R1-23 Suggestion Deferred to next round (batch cap: 7 Criticals first; each answered on its thread)

R2-1 — detector attribution class (fixed)

Reproduced all five entrances against the committed code (probe output: failed exit-0 compound → binds 1234; supersede echo → binds 42 instead of created 100; quoted phrase / multi-line --help / commented-out gh → bind 42 although gh never created anything).

Fix — gh is the attribution authority:

  • Live path (shell.ts): the execution gate (commandRunsGhPrCreate, now exported) is the only remaining text match; the binding is then the PR gh pr view --json number,url resolves for the working branch (fetchCurrentBranchPullRequest in github-prs.ts), accepted only when that URL also appears in the command's output. Text-matched URLs never bind on their own; when gh cannot resolve, nothing binds (fail-closed). This closes the whole forged-URL class at once — including corner A, where the created PR 100 now binds instead of the echoed 42 — and subsumes the deleted --dry-run/last-URL/repo-key gates. Side effect for fork layouts: gh pr view resolves the repo gh actually targets, so live binds work there by construction (noted on the deferred R2-8 thread).
  • Backfill: the transcript-URL source (collectGhPrCreateBindings + gating loop + URL fallback) is deleted entirely. A printed historical URL cannot be attributed to the session's own create, and persisting it is exactly the retroactive forging R2-1 names; what gh cannot vouch for stays unbound (branch mapping + convention still recover history). This also dissolves R2-4: the raw ungated candidate.direct map no longer exists, so no rejected URL can be resurrected.

Witnesses: new shell tests (attribution positive, corner-A flip, three forged shapes, --help gate) and the replaced/added backfill tests; mutation probes: a text-only-binding mutant and an output-gate removal both flip the suite; all restored green.

R2-2 — write-time validation (fixed)

Reproduced on committed code: a 2130-char same-repo URL and an ESC-bearing URL persisted through upsertSessionPr; readSessionPrs then returned null for the WHOLE list and the next upsert rebuilt from [] (prior entry lost). Fix: upsertSessionPr builds the entry and declines it through the same isValidSessionPr the reader uses — one validator, both boundaries. Witnesses: two new tests (over-long URL, control char); probe: removing the check flips both.

R2-3 — cross-run convention eviction (fixed)

Fix: in the backfill loop, an already-bound number with convention authority is re-upserted (moved to the end with a fresh createdAt) while still counting alreadyBound; branch-mapped numbers keep the plain skip. Witness: new two-run test (run 1 binds convention 42 with gh unavailable; run 2 binds 11 branch-mapped PRs) reads the sidecar back and pins 42 at the tail; probe: restoring the plain skip evicts 42 and fails the test.

R2-5 — fork-layout gh-list gate (fixed)

Fix: while building numberToUrl/numberToState/branchToNumber, entries whose URL repo key differs from the workspace origin key are skipped (fork layout: gh pr list resolves the parent repo; unknown remote admits entries as before). Witness: new fork-shape test (parent-repo PR with a colliding head branch binds nothing); on the committed code it binds the stranger's PR (red); probe: removing the gate flips it.

R1-1 — badge never appears in live-state workspaces (fixed)

The child persists the sidecar, then notifies the daemon via a new qwen/notify/session/pr-binding ext-notification (wired in Session.ts through a new SessionService callback seam, mirroring the automatic-title pattern). The BridgeClient demux validates the payload and invokes the EXISTING onSessionCatalogChanged seam — the same catalog-clock mark automatic titles use — so the catalog revision bumps and version-watching clients refetch the binding within the ~2s live-state poll. Witnesses: bridge.test.ts (revision bump on a valid notification; malformed payloads — missing/invalid number/url/sessionId/v — drop without marking); shell.test.ts asserts the emit; probe: removing the catalog mark flips the bridge test.

R1-7 — cross-process sidecar races (fixed)

Fix: every sidecar mutation (upsertSessionPr/updateSessionPrStates share the choke point) now runs under a two-tier lock — the existing in-process queue inside, a proper-lockfile file lock outside — mirroring the mailbox precedent (same retry/stale options shape). Covers the child live binder vs daemon GitDialog/backfill/sweep writers in both directions; the lock targets the sidecar path via its sibling .lock directory, so atomicWriteJSON's rename swap never disturbs it. Witness: new test holds the file lock externally and proves a mutation waits for release instead of interleaving; probe: removing the lock resolves immediately and fails the test.

Deferred suggestions (13)

Batch cap (~8 findings/round, Critical first) deferred R2-6, R2-7, R2-8, R2-9, R1-8, R1-10, R1-12, R1-13, R1-14, R1-15, R1-16, R1-21, R1-23 to the next round; each has a reply on its own thread (comment-replies.json), including interaction notes where this round's restructure changed the seam under the finding (R2-7, R2-8, R1-10, R1-15, R1-23).

Notes

  • Design doc updated to match: gh-attribution mechanism, transcript-URL source removal, fork-layout gh-page gate, convention reposition.
  • Full core suite shows 82 failures in unrelated files (logger, ide-client, editor, storage paths, …) that appear only in the full-parallel run, pass in isolation, and have zero import overlap with this diff — pre-existing concurrency flakes of this runner, not regressions (evidence: logger.test.ts green in isolation; no failing file imports session-pr/shell/github-prs code).
  • Failed checks in the feedback were all Signal the reviewed fork PR: CANCELLED (workflow signal superseded by new pushes), not code-check failures.

Verification

Commands actually run this round (results):

  • npm run build — passed (0 TS errors), re-run after every edit batch
  • npm run typecheck — passed (0 errors)
  • npm run lint — passed (0 errors/warnings)
  • npx prettier --check on all 14 changed source/test files — passed
  • Focused Vitest (core): session-pr-service.test.ts + shell.test.ts + github-prs.test.ts + sessionService.test.ts — 563 passed
  • Focused Vitest (cli): session-pr-backfill.test.ts + session-pr-refresh.test.ts + acp-http/transport.test.ts — 373 passed; Session.test.ts — 667 passed; Session.worktree.test.ts + Session.review-lease.test.ts — 12 passed; server.test.ts + multi-workspace-sessions.test.ts — 1199 passed; full src/acp-integration — 1602 passed
  • Focused Vitest (acp-bridge): bridge.test.ts + bridgeClient.test.ts — 888 passed
  • Reproduction probes vs committed code: R2-1 entrances 1/3/4/5 + corner A all bind forged numbers; R2-2 poison → whole-list null + prior entry lost (probe output quoted above)
  • Mutation probes (each flipped its witness, then restored green): R2-3 reposition guard, R2-5 repo-key gate, R2-2 write validation, shell output-presence gate, gh-attribution vs text-only binding, R1-7 file lock, R1-1 catalog mark, shell emit
  • Integration tests: not run — the touched behavior is exercised by the focused unit suites above, not only through the bundled CLI/integration harness.
中文说明

Autofix 轮次总结 — PR #9739(评审第 3 轮)

增长审计

结论:drift(见 growth-audit.json)。KISS 轴在 gh pr create 检测器周围累积的按角落文本闸门(退出码闸门、最后一个 URL 优先、origin 仓库 key)上判为不通过——R2-1 实测的各入口证明任何文本闸门组合都无法把打印出的 URL 归因到 gh 自身的执行。本轮优先实现了命名的更简替代方案(以 gh 为归因权威、文本匹配收窄为执行闸门),它同时就是 R2-1 的修复。最小变更轴通过:每个 hunk 均可追溯到本 PR 的功能或已接受的发现。本轮尽可能做减法:删除了启发式检测器、transcript URL 回填源和按角落闸门,而不是继续叠加。

反馈处置

发现 严重级 处置
R2-1 (rc:3837318249) Critical 已修复 — 结构化归因
R2-2 (rc:3837318251) Critical 已修复 — 写入时校验
R2-3 (rc:3837318255) Critical 已修复 — 约定号重排
R2-4 (rc:3837318256) Critical 已从设计上消除 — 原始 map 已删除
R2-5 (rc:3837318258) Critical 已修复 — gh 页仓库 key 闸门
R1-1 (rc:3837318261) Critical 已修复pr-binding 通知 → catalog 标记
R1-7 (rc:3837318262) Critical 已修复 — 跨进程 sidecar 锁
R2-6…R2-9、R1-8、R1-10、R1-12…R1-16、R1-21、R1-23 Suggestion 延后到下一轮(批次上限:7 条 Critical 优先;每条均已在各自线程回复)

R2-1 — 检测器归因类问题(已修复)

已对提交代码实测复现全部五个入口(探针输出:退出码为 0 的失败复合命令 → 绑定 1234;覆盖式 echo → 实际创建 100 却绑定 42;引号内短语 / 多行 --help / 注释掉的 gh → gh 什么都没创建却绑定 42)。

修复——以 gh 为归因权威:

  • 实时路径shell.ts):执行闸门(commandRunsGhPrCreate,已导出)是唯一保留的文本匹配;绑定对象改为 gh pr view --json number,urlgithub-prs.tsfetchCurrentBranchPullRequest)为工作分支解析出的 PR,且仅当该 URL 同时出现在命令输出中才绑定。文本匹配出的 URL 永不单独成绑;gh 无法解析时一律不绑(fail-closed)。这一次性关闭了整个伪造 URL 类别——包括 corner A:现在绑定实际创建的 100 而不是被 echo 的 42——并取代了已删除的 --dry-run/最后 URL/仓库 key 闸门。对 fork 布局的附带效果:gh pr view 解析的是 gh 实际 targets 的仓库,因此实时绑定在 fork 场景按构造即可生效(已在延后的 R2-8 线程中注明)。
  • 回填:transcript URL 源(collectGhPrCreateBindings + 闸门循环 + URL 兜底)整体删除。历史打印的 URL 无法归因到会话自身的创建,持久化它正是 R2-1 点名的追溯固化伪造;gh 无法背书的保持不绑(分支映射 + 约定源仍负责恢复存量)。这同时消解了 R2-4:未闸门过滤的原始 candidate.direct map 已不存在,被拒 URL 无从复活。

见证:新增 shell 测试(归因正向、corner A 翻转、三种伪造形态、--help 闸门)与替换/新增的回填测试;变异探针:纯文本绑定变异体与输出去闸门变异体均使套件翻转;恢复后全绿。

R2-2 — 写入时校验(已修复)

已在提交代码上复现:2130 字符同仓库 URL 与含 ESC 的 URL 均可经 upsertSessionPr 持久化;随后 readSessionPrs 对整个列表返回 null,下一次 upsert 从 [] 重建(已有条目丢失)。修复:upsertSessionPr 先构造条目,再用读取侧同款 isValidSessionPr 拒绝不合法条目——一个校验器,两处边界共用。见证:两条新测试(超长 URL、控制字符);探针:移除该校验两条测试均翻转。

R2-3 — 跨运行约定号被挤出(已修复)

修复:回填循环中,已绑定且属约定权威的号码仍会重新 upsert(移到末位、刷新 createdAt),同时保留 alreadyBound 计数;分支映射号码保持普通跳过。见证:新的双运行测试(RUN1 在 gh 不可用时绑定约定 42;RUN2 绑定 11 个分支映射 PR)回读 sidecar 并钉住 42 位于末位;探针:恢复普通跳过会使 42 被挤出、测试失败。

R2-5 — fork 布局 gh 列表闸门(已修复)

修复:构建 numberToUrl/numberToState/branchToNumber 时,URL 仓库 key 与 workspace origin key 不一致的条目一律跳过(fork 布局下 gh pr list 解析的是父仓库;远端未知时仍允许条目通过,与原行为一致)。见证:新的 fork 形态测试(父仓库 PR + 撞名 head 分支,绑定数为 0);在提交代码上该测试为红(会绑定陌生人的 PR);探针:移除闸门使测试翻转。

R1-1 — live-state workspace 中 badge 永不出现(已修复)

子进程持久化 sidecar 后,经新增的 qwen/notify/session/pr-binding ext-notification 通知 daemon(在 Session.ts 中通过 SessionService 新增的回调缝隙接线,复刻自动标题的模式)。BridgeClient 的 demux 校验载荷后调用既有onSessionCatalogChanged 缝隙——与自动标题相同的 catalog 时钟标记——使 catalog revision 递增,版本监听客户端在 ~2s 的 live-state 轮询内 refetch 到绑定。见证:bridge.test.ts(合法通知使 revision 递增;畸形载荷——缺失/非法 number/url/sessionId/v——一律丢弃且不标记);shell.test.ts 断言 emit;探针:移除 catalog 标记使 bridge 测试翻转。

R1-7 — 跨进程 sidecar 竞态(已修复)

修复:所有 sidecar 变更(upsertSessionPr/updateSessionPrStates 共享同一咽喉点)现在运行在两级锁之下——内层为既有的进程内队列,外层为 proper-lockfile 文件锁——复刻 mailbox 先例(重试/stale 参数同形)。覆盖子进程实时绑定与 daemon 侧 GitDialog/回填/扫描写入者的双向竞态;锁经由同级 .lock 目录作用于 sidecar 路径,atomicWriteJSON 的 rename 换文件不会扰动锁。见证:新测试在外部持有文件锁,证明变更会等待释放而不是穿插;探针:移除锁后变更立即完成、测试失败。

延后的建议(13 条)

批次上限(每轮约 8 条、Critical 优先)将 R2-6、R2-7、R2-8、R2-9、R1-8、R1-10、R1-12、R1-13、R1-14、R1-15、R1-16、R1-21、R1-23 延后到下一轮;每条均已在各自线程回复(comment-replies.json),其中对本轮重构改变了发现所依赖缝隙的条目(R2-7、R2-8、R1-10、R1-15、R1-23)附加了交互说明。

备注

  • 设计文档已同步更新:gh 归因机制、transcript URL 源移除、fork 布局 gh 页闸门、约定号重排。
  • core 全量套件中有 82 条失败位于无关文件(logger、ide-client、editor、storage 路径等),仅在全量并行运行时出现、单独运行均通过、且与本 diff 零导入重叠——属于本 runner 既有的并行抖动,并非回归(证据:logger.test.ts 单独运行通过;失败文件均不导入 session-pr/shell/github-prs 代码)。
  • 反馈中的失败检查全部为 Signal the reviewed fork PR: CANCELLED(工作流信号被新推送取代),并非代码检查失败。

验证

本轮实际执行的命令(结果):

  • npm run build — 通过(0 个 TS 错误),每批改动后重跑
  • npm run typecheck — 通过(0 错误)
  • npm run lint — 通过(0 错误/警告)
  • 对全部 14 个改动的源码/测试文件执行 npx prettier --check — 通过
  • 聚焦 Vitest(core):session-pr-service.test.ts + shell.test.ts + github-prs.test.ts + sessionService.test.ts — 563 通过
  • 聚焦 Vitest(cli):session-pr-backfill.test.ts + session-pr-refresh.test.ts + acp-http/transport.test.ts — 373 通过;Session.test.ts — 667 通过;Session.worktree.test.ts + Session.review-lease.test.ts — 12 通过;server.test.ts + multi-workspace-sessions.test.ts — 1199 通过;src/acp-integration 全目录 — 1602 通过
  • 聚焦 Vitest(acp-bridge):bridge.test.ts + bridgeClient.test.ts — 888 通过
  • 针对提交代码的复现探针:R2-1 入口 1/3/4/5 + corner A 全部绑出伪造号码;R2-2 投毒 → 整表 null + 已有条目丢失(探针输出见上文)
  • 变异探针(每项均使其见证翻转、随后恢复全绿):R2-3 重排守卫、R2-5 仓库 key 闸门、R2-2 写入校验、shell 输出在场闸门、gh 归因 vs 纯文本绑定、R1-7 文件锁、R1-1 catalog 标记、shell emit
  • 集成测试:未运行——本轮触及的行为由上述聚焦单测覆盖,并非仅经打包 CLI/集成 harness 行使。

Deferred non-Critical feedback

Critical-only mode is active: the PR's diff grew src 137 / test 450 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 continues to flow unaffected during a growth-only engagement (the per-author batch budget applies only after 5 change-producing rounds). (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:本计数窗口内 diff 净增长已达 源码 137 / 测试 450 行(预算 400/400)。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。纯增长触发期间维护者反馈照常流动(按作者的批次预算仅在完成 5 个产生改动的轮次后生效)。(评论 @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 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:

  • state-rejection 400 message does not name the state constraint (bridge + route) — already reported on the PR in round 1 (comment 3836860820, thread at routes/session.ts:2177); independently re-detected this round by three auditors

Not reviewed: reverse audit — stopped at the reverse-audit round cap of 5 without converging.

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

Not explored to full depth (tool budget reached): chunk 6: executed run of packages/cli/src/serve/server/session-pr-refresh.test.ts (no node_modules/dist in review worktree; install+build not feasible in budget); chunk 3: executing the test file (no node_modules in the review worktree; npm ci plus the prerequisite npm run build for vitest's dist guard exceeded the remaining t….

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

  • packages/acp-bridge/src/bridge.ts:9769 — [review] same-binding no-change check ignores state
  • packages/cli/src/serve/server/session-pr-refresh.ts:161 — [review] env? option on startSessionPrRefreshTimer is a dead switch
  • packages/acp-bridge/src/bridge.ts:9682 — [review] state validation clauses untested at all three write gates
  • packages/cli/src/serve/acp-http/dispatch.ts:2946 — [review] ACP update_metadata state passthrough untested
  • packages/cli/src/serve/server/session-pr-refresh.ts:157 — [review] startSessionPrRefreshTimer timer lifecycle untested
  • packages/cli/src/serve/routes/session-pr-backfill.ts:255 — [probe] alreadyBound+unresolved double count for unresolvable convention numbers
  • packages/cli/src/serve/routes/session-pr-backfill.ts:117 — [review] backfill pagination loop untested
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:179 — [probe] sweep merged-skip test vacuous
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:113 — [probe] sweep archived half untested
  • packages/web-shell/client/components/sidebar/SessionDetailsTooltip.tsx:171 — [probe] tooltip state suffix untested
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:203 — [probe] backfill gh query contract unpinned
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:280 — [probe] non-convention alreadyBound skip untested
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:113 — [probe] sweep pagination loop untested
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:280 — [review] 'without rewriting the sidecar' test name contradicts behavior
  • packages/cli/src/serve/routes/session-pr-backfill.ts:286 — [review] backfill/sweep iterate listAll() including internal live-conversation runtimes
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:46 — [review] draft→open normalization unpinned
  • packages/cli/src/serve/server/session-pr-refresh.test.ts:160 — [review] one-gh-call-per-workspace property unpinned
  • packages/web-shell/client/components/SessionPrBadge.test.tsx:50 — [review] badge tests never pin the base layout class
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:733 — [probe] route aggregation test never asserts the trusted entry
  • packages/cli/src/serve/server/session-pr-refresh.ts:88 — [probe] sweep read-failure isolation guard unpinned
  • …and 2 more (see the run report)

Convergence: round 3 posted 25 inline comment(s), 14 of them reported for the first time; the previous round posted 20 (9 new). Findings keep coming back to the same files: packages/cli/src/serve/routes/session-pr-backfill.ts (findings in rounds 1, 2; 5 more now); packages/cli/src/serve/routes/session-pr-backfill.test.ts (findings in rounds 1, 2; 2 more now); packages/core/src/tools/shell.ts (findings in rounds 1, 2; 2 more now), and 2 more file(s). 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.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had none either, so the next review re-reads the whole diff and will keep doing so until a round's marker carries an anchor again. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

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

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

未审查:reverse audit — stopped at the reverse-audit round cap of 5 without converging。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):chunk 6:executed run of packages/cli/src/serve/server/session-pr-refresh.test.ts (no node_modules/dist in review worktree; install+build not feasible in budget);chunk 3:executing the test file (no node_modules in the review worktree; npm ci plus the prerequisite npm run build for vitest's dist guard exceeded the remaining t…

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

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

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有锚点,因此下一次评审将重读整个 diff——并会一直如此,直到某一轮的标记重新带上锚点。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
Comment thread packages/cli/src/serve/server/session-pr-refresh.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
… execution cwd

Review round 3 for the session PR binding feature:

- Backfill and refresh sweep reject transcript-sourced sessionIds that
  fail isValidSessionId before any sidecar path construction — a planted
  transcript's first record could otherwise read/write sidecars at an
  attacker-chosen path (both consumers share the sink).
- Backfill fails CLOSED when the workspace repo key is unknown: gh may
  resolve a default repo that is not the workspace's, so its page must
  not feed branch mapping (the convention fallback is already disabled
  in that state).
- Backfill stops re-upserting already-bound numbers on every run — that
  moved convention entries to the end with a fresh createdAt, violating
  the binding-time order the badge/tooltip render by. An already-bound
  convention number is restored only when this run's new bindings
  actually evicted it past the tail cap.
- The refresh sweep accepts the repository gh actually queried (the
  page's own URLs name it) in addition to the origin key — fork-layout
  bindings carry parent-repo URLs and were frozen at 'open' forever.
- The backfill route bumps the bridge session-catalog revision when new
  bindings were written, so live-state clients refetch instead of
  waiting for unrelated catalog churn.
- The shell binder attributes `gh pr create` against the execution
  directory (params.directory), not the target dir — a directory
  parameter pointing at another workspace otherwise bound nothing or a
  wrong-repo PR.
…er lend their URL

On an Aone workspace the `/pull/<N>` capture can only match the
fabricated own-remote shape the pre-Aone backfill persisted (real MR
links are codereview/<id>), and the two-segment repo key collapses
nested groups so a sibling project's form passed the gate. A form is
now admitted only when it is exactly this workspace's fabricated shape
(full-path equality via isLegacyFabricated) and supplies the number
only — the URL always comes from mr view, so a dead /pull/ page is never
persisted and nothing needs repairing later (R22-1, R22-2).

@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.

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

Not explored to full depth (tool budget reached): "agent reverse-audit (round 4)": the scope-boundary bullet's claim that fork-PR worktree removal is declined by the pre-existing hasUnmergedWorktreeCommits guard (unchanged core worktree code….

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

Deferred under the convergence posture (round 23, not a blocker) — recorded, not requested in this round; 1 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:

  • packages/cli/src/serve/routes/session-pr-backfill.ts:146 — [probe] Critical [fails-closed] [new-surface] D23-1 REVIEW_COMMAND_PATTERN backtracks quadratically on a spaceless persisted prompt full of https:// anchors without /pull/, stalling…
  • packages/cli/src/serve/routes/session-pr-backfill.ts:620 — [review] R23-2: The isValidSessionPrUrl shape-check disjunct this diff added to URL resolution has a witness only for the /review <url> form leg; the remote-fallback leg — ${re…
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:1059 — [review] R23-3: This diff deletes strips repo-shifting env when resolving the git remote fallback — the only witness that origin-web-URL resolution strips repo-shifting env.…
  • docs/design/2026-08-20-webshell-session-pr-binding.md:39 — [review] R23-6: The new post-hook bullet attributes the retry-misbinding defense to the OPEN-state gate, but the open gate only declines retries resolving a MERGED/CLOSED PR; a retr…
  • docs/design/2026-08-20-webshell-session-pr-binding.md:113 — [review] R23-7: The rewritten scope-boundary bullet says sessions with no worktree sidecar and no /review command are not covered ("回填无可靠来源,不覆盖"), but on Aone such sessions ARE c…
  • packages/cli/src/serve/routes/session-pr-backfill.ts:692 — [review] R23-8: The added ?? urls.get(entry.number) fallback in plannedFor is provably unreachable: the URL-resolution loop populates urls only after if (existingNumbers.has(…
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:427 — [review] R23-9: No test exercises the Aone no-lend short-circuit ( if (resolveAoneUrl) return undefined; in lendableFormUrl ) — the Aone describe never combines a bare /revi…
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:981 — [review] R23-10: The deleted ignores gitBranch keys nested inside structured record values test was the suite's only malformed-transcript-line witness (it seeded a non-JSON l…
  • docs/design/2026-08-20-webshell-session-pr-binding.md:75 — [review] R23-13: The rewritten trim bullet claims entries not re-offered OR not re-resolvable this run become foreign occupants that take slots first ("本轮未再提供、或本轮无法解析的既有条目视为外来占位者先占槽…
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:2334 — [review] R23-14: ignores /review mentions outside user text records does not isolate the record-type gate: the sole assistant-record fixture places /review 55 mid-prose, w…
  • docs/design/2026-08-20-webshell-session-pr-binding.md:65 — [review] R23-16: The source-1 bullet's parenthetical claims a /review <url> form must be the workspace's own repo ("URL 形态须与 workspace 同仓库"), but admission ( allowedRepoKeys , ses…
  • packages/cli/src/serve/routes/session-pr-backfill.ts:359 — [review] R23-19: The added comment asserts the remote fallback is disabled whenever the workspace repo key is unknown ("the remote fallback is already disabled in that state"), but …
  • packages/cli/src/serve/routes/session-pr-backfill.ts:329 — [review] D23-2 dead BackfillCandidate.transcriptPath field: set on every candidate but never read; its JSDoc falsely describes the resurrect guard, which now uses the write-time re-…
  • packages/cli/src/serve/server/aone-mrs.ts:1 — [review] D23-3 aone-mrs module header still claims mr list feeds session-pr-backfill/session-pr-refresh and mentions 'skip branch mapping' — both false at HEAD after this PR removed the last pro…

Convergence: round 23 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: packages/cli/src/serve/routes/session-pr-backfill.ts (findings in round 22; 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.)

[Critical] R22-3 Still standing (originally reported round 18 at packages/cli/src/serve/acp-http/dispatch.ts:3082; not re-anchorable in this round's incremental diff). updateSessionMetadata merges the live entry positionally (.slice(-SESSION_PR_LIST_LIMIT)) and publishes session_metadata_updated carrying that list BEFORE the setSessionPrs reconciliation runs — on the ACP dispatch path and both REST metadata routes — while upsertSessionPr caps by provenance authority. Past the cap the two stores evict different entries, so the same mutation broadcasts the diverged list this reconcile exists to eliminate; setSessionPrs publishes nothing and bumps no catalog revision, so event consumers keep the list missing the session's created binding until unrelated catalog churn. Fix constraint: packages/cli/src/serve/routes/session.ts:5776-5780 — the sidecar write must not move ahead of the bridge mutation, so the corrective event belongs after upsertSessionPr/setSessionPrs. Fix witness: extend the replaces this-daemon-lifetime bindings on setSessionPrs suite in packages/acp-bridge/src/bridge.test.ts (~line 27783) — with a capped mixed-provenance live list, bind via updateSessionMetadata, then call setSessionPrs with the authority-capped list and assert the last session_metadata_updated event's prs equals the reconciled list; removing the publish turns it red.

中文说明

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 4)"the scope-boundary bullet's claim that fork-PR worktree removal is declined by the pre-existing hasUnmergedWorktreeCommits guard (unchanged core worktree code…

11 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论;CLI 已将其移入下方延后清单(下限强制执行)。

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

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

[Critical] R22-3 Still standing (originally reported round 18 at packages/cli/src/serve/acp-http/dispatch.ts:3082; not re-anchorable in this round's incremental diff). updateSessionMetadata merges the live entry positionally (.slice(-SESSION_PR_LIST_LIMIT)) and publishes session_metadata_updated carrying that list BEFORE the setSessionPrs reconciliation runs — on the ACP dispatch path and both REST metadata routes — while upsertSessionPr caps by provenance authority. Past the cap the two stores evict different entries, so the same mutation broadcasts the diverged list this reconcile exists to eliminate; setSessionPrs publishes nothing and bumps no catalog revision, so event consumers keep the list missing the session's created binding until unrelated catalog churn. Fix constraint: packages/cli/src/serve/routes/session.ts:5776-5780 — the sidecar write must not move ahead of the bridge mutation, so the corrective event belongs after upsertSessionPr/setSessionPrs. Fix witness: extend the replaces this-daemon-lifetime bindings on setSessionPrs suite in packages/acp-bridge/src/bridge.test.ts (~line 27783) — with a capped mixed-provenance live list, bind via updateSessionMetadata, then call setSessionPrs with the authority-capped list and assert the last session_metadata_updated event's prs equals the reconciled list; removing the publish turns it red.

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

Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
…the run outranks it

The provenance-ranked trim protected a re-offered occupant at
max(this-run stamp, persisted source) but wrote it back verbatim, so a
`/review`-bound number the session now exists for via the pr-<N>
convention stayed at review rank on disk and the next capped upsert
evicted the session's own PR first. The planner now promotes such an
entry in place — url, createdAt, position untouched, never downgraded,
a new object so the no-op check commits — mirroring upsertSessionPrs'
same-URL upgrade (R23-1). A pre-provenance convention occupant gets one
migration write and is idempotent afterwards; a `/review` re-mention of
a source-less entry is not an upgrade and writes nothing.

@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.

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

  • R24-1 dead BackfillCandidate.transcriptPath field with misleading doc comment — already reported (round 23 deferral list, review 5057458127, as D23-2)
  • R24-2 unreachable urls.get fallback in plannedFor — already reported (round 23 deferral list, review 5057458127, as R23-8)
  • R24-3 env-stripping witness deleted; fetchRemoteWebUrl spawn env untested — already reported (round 23 deferral list, review 5057458127, as R23-3)
  • R24-4 remote-derived leg of the URL-validity check unwitnessed — already reported (round 23 deferral list, review 5057458127, as R23-2)
  • R24-5 record-type gate witness vacuous (mid-prose assistant fixture) — already reported (round 23 deferral list, review 5057458127, as R23-14)
  • R24-6 doc misattributes retry-misbind blocking to the open gate — already reported (round 23 deferral list, review 5057458127, as R23-6)
  • R24-7 doc self-contradiction on the URL-form repo gate — already reported (round 23 deferral list, review 5057458127, as R23-16)

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally; the local build-test run also never reached the packages/cli suite (non-incremental builds exhausted the budget before any suite ran; the packages/sdk-typescript build timed out), and its test-efficacy probe was inconclusive (no green baseline). CI is red at the reviewed commit: Test (ubuntu-latest, Node 22.x), Serve A/B (ubuntu-latest, Node 22.x) and ubuntu-latest / Java 11 failing..

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

  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:2819 — [probe] D24-1 zero-number guard witnesses masked by the writer-side filter; the :208 urlNumber guard has no witness at all

Convergence: round 24 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: packages/cli/src/serve/routes/session-pr-backfill.ts (findings in round 23; 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 (2 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.

[Critical] R22-3 Still standing (originally reported round 18 at packages/cli/src/serve/acp-http/dispatch.ts:3082; not re-anchorable in this round's incremental diff). updateSessionMetadata merges the live entry positionally (.slice(-SESSION_PR_LIST_LIMIT)) and publishes session_metadata_updated carrying that list BEFORE the setSessionPrs reconciliation runs — on the ACP dispatch path and both REST metadata routes — while upsertSessionPr caps by provenance authority. Past the cap the two stores evict different entries, so the same mutation broadcasts the diverged list this reconcile exists to eliminate; setSessionPrs publishes nothing and bumps no catalog revision, so event consumers keep the list missing the session's created binding until unrelated catalog churn. Re-verified at HEAD f3e41ac: dispatch.ts:3036-3085 calls bridge.updateSessionMetadata (which publishes the positionally capped list at bridge.ts:10411-10431) before upsertSessionPr + setSessionPrs; both REST metadata routes do the same (session.ts:5780-5801 and 5957-5972); setSessionPrs (bridge.ts:10448-10457) publishes no event and bumps no revision. Witness: not run — this round's re-check is a code trace of the ordering at HEAD; the nearest capability, a probe driving updateSessionMetadata + setSessionPrs with a capped mixed-provenance list, already settled the divergence in the original round-18 probe quoted in the thread.

中文说明

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

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally; the local build-test run also never reached the packages/cli suite (non-incremental builds exhausted the budget before any suite ran; the packages/sdk-typescript build timed out), and its test-efficacy probe was inconclusive (no green baseline). CI is red at the reviewed commit: Test (ubuntu-latest, Node 22.x), Serve A/B (ubuntu-latest, Node 22.x) and ubuntu-latest / Java 11 failing.。

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

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

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

[Critical] R22-3 Still standing (originally reported round 18 at packages/cli/src/serve/acp-http/dispatch.ts:3082; not re-anchorable in this round's incremental diff). updateSessionMetadata merges the live entry positionally (.slice(-SESSION_PR_LIST_LIMIT)) and publishes session_metadata_updated carrying that list BEFORE the setSessionPrs reconciliation runs — on the ACP dispatch path and both REST metadata routes — while upsertSessionPr caps by provenance authority. Past the cap the two stores evict different entries, so the same mutation broadcasts the diverged list this reconcile exists to eliminate; setSessionPrs publishes nothing and bumps no catalog revision, so event consumers keep the list missing the session's created binding until unrelated catalog churn. Re-verified at HEAD f3e41ac: dispatch.ts:3036-3085 calls bridge.updateSessionMetadata (which publishes the positionally capped list at bridge.ts:10411-10431) before upsertSessionPr + setSessionPrs; both REST metadata routes do the same (session.ts:5780-5801 and 5957-5972); setSessionPrs (bridge.ts:10448-10457) publishes no event and bumps no revision. Witness: not run — this round's re-check is a code trace of the ordering at HEAD; the nearest capability, a probe driving updateSessionMetadata + setSessionPrs with a capped mixed-provenance list, already settled the divergence in the original round-18 probe quoted in the thread.

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

Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
Plan membership inherits plannedFor's same-PR check, which fails open
on GitHub when the number cannot be resolved this run — fine for the
reversible trim, not for a permanent provenance stamp: a foreign
same-numbered occupant at the convention number would have become the
session's highest-authority binding. The promotion now requires
attested identity, the bar upsertSessionPrs sets for its upgrade: the
entry's canonical url is the one gh resolved for the number, or this
workspace's own <remote>/pull/<N> shape (a url names its repository by
path, so a pre-provenance convention binding still migrates offline),
or on Aone the exact detailUrl shape. Anything else stays untouched
until a later run can attest it (R23-1 follow-up).
…eate

Resolves sessionService with QwenLM#10300 (cleanup ownership): main's
moveArchiveSidecars helper and assertCleanupOwned naming are kept, and
the PR sidecar leg inside it runs the locked moveSessionPrSidecar this
branch introduced instead of the unlocked private movePrSidecar (which
does not come back). The ownership tests written against the private
method now assert the fence is handed to the locked move and that a
SessionWriterLostError rejection from it is not downgraded to a
warning; the split-pair merge cases stay pinned in
session-pr-service.test.ts.
@wenshao

wenshao commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ incomplete — cancelled - workflow run

The verification job was cancelled before producing a report.

中文 — 判定:⚠️ 未完成 · 已取消

验证作业在生成报告前被取消。

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@wenshao

wenshao commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ⚠️ incomplete — cancelled - workflow run

The verification job was cancelled before producing a report.

中文 — 判定:⚠️ 未完成 · 已取消

验证作业在生成报告前被取消。

Qwen Code · sandboxed verification

@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.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally (the local build-test run also never reached any test suite: the shared budget was exhausted by non-incremental workspace builds before the test phase began, and the test-efficacy probe was inconclusive — no green baseline).

Not reviewed: chunk 10 — execute packages/cli/src/serve/routes/session-pr-backfill.test.ts (new backfillWorkspaceSessionPrs cases) to confirm they pass — stopped at the agent tool budget.

Not explored to full depth (tool budget reached): chunk 10: execute packages/cli/src/serve/routes/session-pr-backfill.test.ts (new backfillWorkspaceSessionPrs cases) to confirm they pass — worktree lacks node_modules and…; "agent test-matrix": none — all assigned diff ranges were read in full and every candidate was verified against the worktree before filing..

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

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

  • packages/cli/src/serve/fast-path-open.test.ts:113 — [review] vi.waitFor flake fix applied to 3 of 7 identical sites
  • packages/cli/src/serve/routes/session-pr-backfill.ts:329 — [review] dead BackfillCandidate.transcriptPath field
  • packages/core/src/services/sessionService.ts:2461 — [review] listAllProjectSessionIds silently truncates at 10000
  • packages/core/src/utils/github-prs.ts:28 — [review] mapEntry comment contradicts the added state field
  • packages/cli/src/serve/routes/session.ts:5807 — [review] source:'create' route stamps asserted nowhere
  • packages/core/src/utils/github-prs.ts:490 — [review] fetchRemoteWebUrl env sanitization has no witness
  • packages/cli/src/serve/routes/session-pr-backfill.ts:257 — [review] spawns run before the first assertGenerationOpen()
  • packages/cli/src/acp-integration/session/Session.ts:8990 — [review] Session pr-bound callback link has no test
  • packages/core/src/services/session-pr-service.ts:723 — [review] ownership fence in moveSessionPrSidecar unwitnessed
  • packages/core/src/tools/shell.test.ts:297 — [review] fixed 20ms sleep hides delayed wrong bindings
  • packages/cli/src/serve/routes/session-pr-backfill.ts:798 — [review] fork-layout attested promotion unreachable
  • docs/design/2026-08-20-webshell-session-pr-binding.md:39 — [review] doc omits headRefName from the gh pr view quote
  • docs/design/2026-08-20-webshell-session-pr-binding.md:39 — [review] doc misattributes retry blocking to the open gate
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:194 — [review] orphaned origin/HEAD symref seeding in three seeders
  • packages/cli/src/serve/routes/session-pr-backfill.ts:146 — [review] REVIEW_COMMAND_PATTERN rejects the ?review prefix
  • packages/core/src/tools/shell.test.ts:722 — [review] promote-scope test cannot detect a post-run leg
  • packages/core/src/tools/shell.ts:2367 — [review] pre-spawn snapshot uninterruptible up to ~10s on cancel
  • packages/core/src/services/session-pr-service.ts:181 — [review] gate admits VAR=value after sudo/nohup/command
  • packages/core/src/utils/github-prs.test.ts:581 — [review] spawn opts (timeout/cwd) unasserted on new sites
  • packages/core/src/utils/github-prs.ts:408 — [review] JSDoc says without a port; implementation keeps http(s) ports
  • …and 5 more (see the run report)
中文说明

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally (the local build-test run also never reached any test suite: the shared budget was exhausted by non-incremental workspace builds before the test phase began, and the test-efficacy probe was inconclusive — no green baseline)。

未审查:chunk 10 — execute packages/cli/src/serve/routes/session-pr-backfill.test.ts (new backfillWorkspaceSessionPrs cases) to confirm they pass — stopped at the agent tool budget。

未探索到全部深度(达到工具调用预算):chunk 10:execute packages/cli/src/serve/routes/session-pr-backfill.test.ts (new backfillWorkspaceSessionPrs cases) to confirm they pass — worktree lacks node_modules and…"agent test-matrix"none — all assigned diff ranges were read in full and every candidate was verified against the worktree before filing.

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

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

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

Comment thread packages/cli/src/serve/routes/session.ts
…ion changes the live pr list

Past the cap the bridge's positional merge and the sidecar's
provenance-ranked cap evict different entries, so the mutation's own
session_metadata_updated event carried the pre-reconcile list while
setSessionPrs installed the authoritative one silently — event-stream
consumers kept the diverged list until unrelated catalog churn, and a
revision-gated refetch landing in the bump→rewrite window cached it
with no re-trigger. setSessionPrs now compares the incoming list with
the live entry and, only when membership actually changed, publishes a
corrective session_metadata_updated (displayName echoed, full prs) and
advances the catalog revision. All reconcile call sites — both metadata
routes, the ACP dispatch path, and backfill's live-entry sync — get the
corrective event centrally; the matching-list case stays silent, so
below-cap flows publish exactly as before (R22-3/R18-2).

@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.

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

Not explored to full depth (tool budget reached): chunk 13: none — no checks were cut short.; "agent invariant-b (packages/core/src/utils/github-prs.ts)": none — the walk completed within budget..

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

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

  • packages/cli/src/serve/routes/session-pr-backfill.ts:329 — [review] dead transcriptPath field with misleading comment
  • packages/cli/src/serve/routes/session-pr-backfill.ts:692 — [review] plannedFor's urls fallback is unreachable
  • packages/cli/src/serve/routes/session-pr-backfill.ts:801 — [review] fork-parent page URLs can never attest promotion
  • packages/core/src/services/sessionService.ts:2458 — [review] silent 10k truncation in session enumeration
  • packages/cli/src/serve/acp-http/dispatch.ts:3097 — [review] route source:'create' stamp unpinned
  • packages/cli/src/acp-integration/session/Session.ts:9302 — [review] Session pr-binding notification leg untested
  • packages/core/src/utils/github-prs.ts:484 — [review] GIT_DIR stripping lost its spawn-level witness
  • packages/core/src/services/session-pr-service.ts:347 — [review] mergeSessionPrLists authority cap unexercised
  • packages/acp-bridge/src/bridge.ts:11508 — [review] setSessionPrs displayName echo unpinned
  • packages/cli/src/serve/acp-http/dispatch.ts:3115 — [review] rename-only metadata replies serve stale prs list
  • packages/core/src/services/session-pr-service.test.ts:264 — [review] lock canonicalization witness platform-dependent
  • packages/core/src/services/session-pr-service.test.ts:742 — [review] batch upsert poison-URL guard untested
  • packages/core/src/services/session-pr-service.ts:257 — [review] env -i misses command-established GH credentials
  • packages/core/src/services/sessionService.test.ts:2626 — [review] moveSessionPrSidecar ownership fence untested
  • packages/core/src/tools/shell.test.ts:188 — [review] active archive arm exercised only via catch
  • packages/core/src/tools/shell.ts:2366 — [review] GH_REPO-redirected creates can never bind live
  • packages/core/src/utils/github-prs.test.ts:534 — [review] new fetcher tests never assert spawn cwd
  • packages/core/src/services/session-pr-service.ts:190 — [review] quote-blind split fails open on quoted separators
  • packages/core/src/tools/shell.test.ts:695 — [review] promoted-scope binding test vacuous
  • packages/core/src/tools/shell.test.ts:895 — [review] archived re-resolution ordering unpinned
  • …and 1 more (see the run report)

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

中文说明

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):chunk 13:none — no checks were cut short."agent invariant-b (packages/core/src/utils/github-prs.ts)"none — the walk completed within budget.

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

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

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

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

Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts Outdated
Comment thread packages/core/src/services/session-pr-service.ts
mergeSessionPrLists kept the freshest same-number entry wholesale — the
one binding writer left that could downgrade provenance: the live shell
binder stamps create on one half of a split pair, a later /review
backfill re-binds the number as review on the other half, and the next
archive transition's merge handed the authority cap a rank-0 create
binding to evict. The dedup now carries the stronger source across a
same-canonical-url pair (freshest createdAt still wins the slot; a
different url is another PR and carries nothing), mirroring
upsertSessionPr (R26-27).
… url forms

URL-form review numbers were appended after every bare number, so the
trim's same-rank position tie-break — the age proxy mirroring the
sidecar cap's list-order rule — evicted the second-oldest review while
the genuinely oldest (a url form typed first) survived at the newest
position, permanently re-derived on every run. collectReviewedPrNumbers
now emits one ordered mention stream (first occurrence wins the slot,
bare-ness sticks), the plan follows it, and a number mentioned only
through forms still plans only when a form passed the gate (R26-1).

@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.

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

Not reviewed: build-and-test — build-cli was skipped in CI and the local packages/cli build timed out on a shrunken whole-call budget (the cli suites themselves ran green via vitest, 100/100).

Not explored to full depth (tool budget reached): chunk 7: executing session-pr-backfill.test.ts to confirm the suite is green — the review worktree has no node_modules or built workspace-package dist/ , and a full…; chunk 12: executing session-pr-service.test.ts under vitest — the shared review worktree has no node_modules installed and vitest cannot start; every test in my chunk….

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

  • packages/cli/src/serve/routes/session-pr-backfill.ts:719 — [review] Dead ?? urls.get() fallback in plannedFor (R27-2)
  • packages/core/src/utils/github-prs.ts:490 — [review] fetchRemoteWebUrl env-stripping lost its only witness (R27-4)
  • packages/core/src/services/sessionService.ts:2461 — [review] Silent 10,000-session truncation in listAllProjectSessionIds, unpinned, doc contradicts (R27-6)
  • packages/core/src/services/session-pr-service.ts:669 — [review] upsertSessionPrs invalid-entry decline branch unwitnessed (R27-7)
  • packages/cli/src/serve/routes/session-pr-backfill.ts:349 — [review] Dead BackfillCandidate.transcriptPath field (R27-8)
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:2625 — [review] functionResponse record gate never exercised with /review content (R27-9)
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:3090 — [review] First-mention-wins position retention unpinned under cap pressure (R27-12)
  • packages/cli/src/serve/routes/session-pr-backfill.ts:520 — [review] Typed www-form URLs admitted by repo key, persisted verbatim, never match canonically (R27-17)
  • packages/core/src/services/session-pr-service.test.ts:504 — [review] Batch writer's no-state-carry-over boundary unwitnessed (R27-19)
  • packages/core/src/services/session-pr-service.test.ts:620 — [review] sessionPrSourceAuthority default-rank upper half unwitnessed (R27-20)
  • packages/core/src/services/session-pr-service.ts:181 — [review] Shell command model diverges from bash: six probed entrances (class finding, R27-28)
  • packages/cli/src/serve/routes/session-pr-backfill.test.ts:1478 (+3 locations) — [review] Three stale branch-mapping comments in the cap tests (pattern x3, R27-29)

Convergence: round 27 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: packages/cli/src/serve/routes/session-pr-backfill.ts (findings in round 26; 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.)

中文说明

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

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:build-and-test — build-cli was skipped in CI and the local packages/cli build timed out on a shrunken whole-call budget (the cli suites themselves ran green via vitest, 100/100)。

未探索到全部深度(达到工具调用预算):chunk 7:executing session-pr-backfill.test.ts to confirm the suite is green — the review worktree has no node_modules or built workspace-package dist/ , and a full…;chunk 12:executing session-pr-service.test.ts under vitest — the shared review worktree has no node_modules installed and vitest cannot start; every test in my chunk…

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

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

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

Comment thread packages/cli/src/serve/routes/session-pr-backfill.ts
…eate

Resolves the session-pr surface with QwenLM#10425 (issue bindings derived
from the closing references of bound PRs). Union merges: SessionPr
carries both this branch's provenance and main's issue snapshot (the
validators check both; upsertSessionPr keeps the snapshot on the
same-PR condition next to the authority cap and source upgrade),
updateSessionPrStates takes main's fetched-snapshot form, the bridge
projects through toSessionPrInfo with the corrective publish comparing
issues too, and the metadata routes keep this branch's setSessionPrs
reconcile after main's shared projection. mergeSummaryPrs unifies the
two models: sidecar supplies order and the cap gate, a canonical-equal
live spelling wins with the sidecar's state/issues overlaid, a
canonically different url is another PR and the sidecar row wins
wholesale with its own snapshot (sidecar-only writers never touch the
live entry; the route window reconciles in-request), and a live binding
attaches to the url-matched entry when a hand-edited sidecar duplicates
a number. Two pre-existing tests were re-pinned to the unified
semantics; main's new canonical-equal and duplicate-number tests pass
unchanged.
…date

In the fork layout the workspace's own numbers live on the CONFIRMED
parent page: numberToUrl is gated to the fork's repo key and the remote
shape is the fork's url, so the provenance promotion's attestation could
never succeed there and the session's own convention binding stayed at
its weak persisted rank forever. attested() gains the third identity
form — the trusted page's url for the number, canonical-compared, gated
on pageMapTrusted so a divergent page still attests nothing (R27-1).
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@wenshao

wenshao commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 5187 passed · 0 failed · 5187 total

Flakiness gate: ⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:5187 通过 · 0 失败 · 5187 总计

抖动门:⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

Verification report

PR 9739 — deep verification

Verdict: findings — 5187/5187 scripted assertions passed; 1 high finding (catastrophic regex backtracking in the new execution gate) and 1 suggestion (description/test-plan stale vs shipped code). Verified head 0a3bfcaa65414f7502cf68b6646bf04c9a124f9e (merge base b7dbea33e9).

中文摘要
  • 结论:findings。核心行为(会话内 gh pr create 成功后写入 PR sidecar 绑定)经 A/B 证实:head 构建在 11 个场景中绑定/拒绝全部符合预期(38/38),base 构建完全不绑定(28/28),翻转即本 PR 的作用。
  • 高优先级发现:新引入的执行门正则 GH_PR_CREATE_SEGMENT_PATTERNenv -i GH_A=b 重复形状指数回溯——24 次重复(337 字符)8.6 s,26 次(365 字符)超过 20 s 且 SIGTERM 无效、必须 SIGKILL。该门对所有前台 shell 命令运行,冻结不可恢复。已给出并实测修复(加一个负向前瞻消除歧义):恶意形状 0 ms,良性 41 项扫描与绑定行为逐字节不变。
  • 描述与代码不符(建议):PR 正文声称的"回溯式 backfill 第三来源(配对 run_shell_command 调用/响应)"和"检测器排除 --dry-run"在最终代码中均不存在——前者被有意删除(transcript 文本无 gh 归因,属伪造向量),dry-run 安全改由 gh 自身校验保证(已实测 dry-run 不绑定)。Reviewer Test Plan 的 backfill 步骤因此无法执行,需要更新描述。
  • 未覆盖:daemon 全链路徽章渲染(仅静态验证接线 + emit 侧实测)、Windows 拼写的真实 shell 执行、逐 commit 归因(shallow checkout)、archived sidecar 路径。
  • 门禁:core 1392 + cli 2726 + acp-bridge 886 = 5004/5004 绿;4/4 突变体被对应测试杀死,无空转测试。

Central claim + A/B

Central claim: a foreground gh pr create run in the session shell binds the session↔PR (sidecar entry source: 'create', state open, catalog notification emitted), and the attribution gates decline every shape that did not actually create a PR.

Harness: real ShellToolInvocation from the built dist/ (head) vs a control build compiled from HEAD^1, driven with a real fake gh binary (derives repo identity live from git remote, records every leg's argv + credential env), a real git repo per cell, and the real SessionService/sidecar persistence. No part of the unit under test is stubbed. Raw logs: logs/head-final.log, logs/base-final.log.

cell (head) command expected observed
create-happy gh pr create --fill bound 42/open/create, emit 1 bound, emit 1
retry-existing gh pr create --fill || gh pr view … (PR 42 pre-existed) declined declined
branch-switch git checkout -qb other && gh pr create --fill declined (headRef ≠ pre-run branch) declined
retarget-origin git remote set-url origin …/evil/r.git && gh pr create --fill declined (repo key) declined
dry-run gh pr create --fill --dry-run declined (no PR post-run) declined
failed-then-ok gh pr create --fill || true (create errors) declined declined
output-redirected gh pr create --fill > pr-out.txt declined (output gate) declined
merged-state create records state MERGED declined (state gate) declined
inline-token GH_TOKEN=t0ken gh pr create --fill bound + all 3 gh legs carry t0ken bound, legs authenticated
already-bound sidecar pre-seeded with 42@​same URL kept, no re-stamp, no emit createdAt preserved
mention-not-exec grep -rn 'gh pr create' notes.txt not a create ignored

Head arm 38/38; base arm 28/28 — on base the two positive cells do not bind and a pre-written sidecar survives untouched without a source field. The flip is load-bearing: 01-ab-head.png / 02-ab-base.png.

Secondary claims verified: detector grammar sweep 41/41 (03-series checks in logs/detector-sweep.log: wrappers, path-qualified and Windows spellings, segment splitting, negative shapes like bash -c/timeout/mentions); provenance authority and eviction caps pinned by the PR's own tests (mutation matrix below).

Findings

F1 (high) — the new execution gate regex has catastrophic backtracking; a ~365-char command freezes the agent process, unrecoverable by SIGTERM

commandRunsGhPrCreate() runs on every foreground shell command (pre-spawn snapshot gate in shell.ts), and GH_PR_CREATE_SEGMENT_PATTERN's wrapper group admits two parses for env -i GH_A=b (flag-with-value vs flag + assignment), giving exponential backtracking on a failing tail:

reps of "env -i GH_A=b " + "X":  18→117 ms  20→504 ms  22→2.0 s  24→8.6 s  26→>20 s (SIGKILL)

03-redos-original.png. At 26 reps (365 chars) the process never returns; timeout 20 (SIGTERM) does not kill it — the synchronous backtrack blocks the event loop so the signal is never processed (observed twice: a 440 s hang and a 120 s probe). Only SIGKILL ends it. There is no timeout around the gate itself (it runs before the child spawn the shell timeout would govern).

Reachability: the command string is model-authored; prompt-injected content (repo files, issue/PR bodies) is the realistic writer. The input is fully grammar-plausible (env -i VAR=val chains are exactly the shape the gate advertises support for), so this is not a contrived encoding. Blast radius: the session's tool execution freezes permanently; in serve mode the frozen process is the session child (daemon survives — not demonstrated end-to-end, see Not covered).

Measured fix (scratch copy of the built module, not applied to the repo): make the flag's optional value refuse an assignment, removing the ambiguity —

-  (?:sudo|env|nohup|command)\s+(?:-\S+(?:\s+\S+)?\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+){0,3})*
+  (?:sudo|env|nohup|command)\s+(?:-\S+(?:\s+(?![A-Za-z_][A-Za-z0-9_]*=)\S+)?\s+|[A-Za-z_][A-Za-z0-9_]*=\S+\s+){0,3})*

On the patched build: 24/26/40/100 reps and 20 000 chars all 0 ms (04-redos-fixed.png); the 41-check benign sweep is byte-identical green, and the binding cells (create-happy 7/7, retry 2/2, inline-token 8/8, dry-run 2/2) still pass — zero collateral. sudo -u runner … / env -u GH_TOKEN … still match.

F2 (suggestion) — the description and Reviewer Test Plan describe code that was removed during review

  • The body's retroactive path ("the on-demand backfill route gains a third source that pairs each run_shell_command call with its response … recovering PRs created before the live hook existed") does not exist in the shipped code. backfillWorkspaceSessionPrs states the opposite by design: "Transcript gh pr create traces … are deliberately NOT sources: text alone carries no gh-side attribution (a forged binding vector)". The removal is correct security-wise; the description and the "re-run POST /sessions/backfill-prs … confirm bound counts them" test-plan step are stale and the step is now unperformable.
  • "excluding --dry-run" is likewise absent from the detector; dry-run safety is instead enforced by the gh-verified gate (my dry-run cell: declines). Behavior is safe; the prose mechanism is wrong.
  • "extracts the PR URL that gh prints on success and writes the sidecar directly" understates the shipped design: gh itself is the attribution authority (pre-run snapshot vs post-run resolution, repo-key + branch + state gates); the printed URL is only one gate. This is a correction to the description, not a request to change the code.

F3 (low) — repoKeyFromWebUrl drops the port; same-host multi-port GHE instances collide as one key

repoKeyFromWebUrl uses url.hostname (port stripped) while normalizeRemoteToWebUrl keeps https ports. Every comparison site is key-vs-key (verified at shell.ts:3180, github-prs.ts:673/678, and all five backfill sites), so the strip is symmetric and the binder comparison holds for port-carrying GHE (asserted in the sweep). Residual: https://ghe.corp:8443/team/repo and https://ghe.corp:9999/team/repo produce the same key, so a mid-run remote set-url between two instances of the same host/path would pass the repo gate (still blocked unless the other gates also pass). Edge of an edge; note, not a blocker.

Mutation matrix (vacuity of the PR's own tests)

mutant hunk suite result
A1 bind call site disabled (if (false)) shell.test.ts -t "gh pr create binding" killed — 7 red, intended assertion (expected "spy" to be called … Number of calls: 0)
A2 output gate inverted (if (output.includes(url)) return) same killed — 8 red
B1 no-downgrade guard >=< session-pr-service.test.ts killed — exactly the 2 pinning tests red (05-mutation-b1.png)
B2 create authority 2 → 0 same killed — 5 red (cap/eviction/split-pair tests)

No survivors; no coverage gaps to classify. Unmutated control green (5004/5004 below).

Targeted gates

workspace files result
core shell, session-pr-service, github-prs, sessionService, gitWorktreeService, exit-worktree, config tests 1392/1392
cli session-pr-backfill, server 1271/1271
cli Session×3, transport, fast-path-open, aone-mrs, session-pr-refresh, worktreeStartup, AppContainer 1455/1455
acp-bridge bridge.test.ts 886/886

Not covered

  • Full daemon E2E (badge render / ~2 s refetch): wiring verified statically (Session.ts:9320qwen/notify/session/pr-binding); the emit side was exercised live in the harness. Driving a real daemon + client was beyond budget.
  • Windows spellings (gh.exe, C:\…) verified at detector level only; no Windows shell spawn in this container.
  • Per-commit attribution: shallow checkout (depth 2) — 70 commits in metadata, 1 reachable; verified the aggregate HEAD^1..HEAD diff only.
  • Archived-session sidecar write path not driven by the harness (active only); moveSessionPrSidecar covered by its suite.
  • Aone MR surface (aone-mrs.ts) covered by its test suite only, not behaviorally.
  • Base control compiled base core against head's root node_modules (lockfile diff is a reorder only — no dependency-version confound); base build logged one benign TS7016 (@lydell/node-pty d.ts via a tsconfig paths pointing outside the worktree) — JS still emitted and verified pre-PR (grep -c bindGhPrCreate = 0).

Methodology

CI merge-ref checkout (HEAD merge, HEAD^1 base, HEAD^2 head). Harnesses live in harness/ (ab-harness.mjs, fake-gh.mjs, detector-sweep.mjs, redos-rung.mjs) and are rerunnable; raw logs in logs/. The A/B drove the real built ShellToolInvocation with a fake gh on PATH and real git repos; oracles are the sidecar JSON, the sessionPrBound callback, and the gh call journal. Assertion tally: A/B head 38 + base 28 + sweep 41 + ReDoS original 6 + fix sweep 41 + fix ladder 6 + fix cells 19 + mutant predictions 4 + vitest gates 5004 = 5187 pass, 0 fail.

Flakiness gate log

rounds=5 files=19 skipped=0
file packages/acp-bridge/src/bridge.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/bridge.test.ts
file packages/cli/src/acp-integration/session/Session.review-lease.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.review-lease.test.ts
file packages/cli/src/acp-integration/session/Session.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.test.ts
file packages/cli/src/acp-integration/session/Session.worktree.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.worktree.test.ts
file packages/cli/src/serve/acp-http/transport.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/transport.test.ts
file packages/cli/src/serve/fast-path-open.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/fast-path-open.test.ts
file packages/cli/src/serve/routes/session-pr-backfill.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-pr-backfill.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/cli/src/serve/server/aone-mrs.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/aone-mrs.test.ts
file packages/cli/src/serve/server/session-pr-refresh.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/session-pr-refresh.test.ts
file packages/cli/src/startup/worktreeStartup.test.ts: (cd packages/cli) npx --no-install vitest run ./src/startup/worktreeStartup.test.ts
file packages/cli/src/ui/AppContainer.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/AppContainer.test.tsx
file packages/core/src/config/config.test.ts: (cd packages/core) npx --no-install vitest run ./src/config/config.test.ts
file packages/core/src/services/gitWorktreeService.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/gitWorktreeService.test.ts
file packages/core/src/services/session-pr-service.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/session-pr-service.test.ts
file packages/core/src/services/sessionService.test.ts: (cd packages/core) npx --no-install vitest run ./src/services/sessionService.test.ts
file packages/core/src/tools/exit-worktree.test.ts: (cd packages/core) npx --no-install vitest run ./src/tools/exit-worktree.test.ts
file packages/core/src/tools/shell.test.ts: (cd packages/core) npx --no-install vitest run ./src/tools/shell.test.ts
file packages/core/src/utils/github-prs.test.ts: (cd packages/core) npx --no-install vitest run ./src/utils/github-prs.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridge.test.ts: PP
  packages/cli/src/acp-integration/session/Session.review-lease.test.ts: PP
  packages/cli/src/acp-integration/session/Session.test.ts: PP
  packages/cli/src/acp-integration/session/Session.worktree.test.ts: PP
  packages/cli/src/serve/acp-http/transport.test.ts: PP
  packages/cli/src/serve/fast-path-open.test.ts: PP
  packages/cli/src/serve/routes/session-pr-backfill.test.ts: PP
  packages/cli/src/serve/server.test.ts: PP
  packages/cli/src/serve/server/aone-mrs.test.ts: P
  packages/cli/src/serve/server/session-pr-refresh.test.ts: P
  packages/cli/src/startup/worktreeStartup.test.ts: P
  packages/cli/src/ui/AppContainer.test.tsx: P
  packages/core/src/config/config.test.ts: P
  packages/core/src/services/gitWorktreeService.test.ts: P
  packages/core/src/services/session-pr-service.test.ts: P
  packages/core/src/services/sessionService.test.ts: P
  packages/core/src/tools/exit-worktree.test.ts: P
  packages/core/src/tools/shell.test.ts: P
  packages/core/src/utils/github-prs.test.ts: P

verdict: timeout
summary: the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.review-lease.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.worktree.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/transport.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/fast-path-open.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-pr-backfill.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/aone-mrs.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/session-pr-refresh.test.ts: P (exit 0)
round 1 · packages/cli/src/startup/worktreeStartup.test.ts: P (exit 0)
round 1 · packages/cli/src/ui/AppContainer.test.tsx: P (exit 0)
round 1 · packages/core/src/config/config.test.ts: P (exit 0)
round 1 · packages/core/src/services/gitWorktreeService.test.ts: P (exit 0)
round 1 · packages/core/src/services/session-pr-service.test.ts: P (exit 0)
round 1 · packages/core/src/services/sessionService.test.ts: P (exit 0)
round 1 · packages/core/src/tools/exit-worktree.test.ts: P (exit 0)
round 1 · packages/core/src/tools/shell.test.ts: P (exit 0)
round 1 · packages/core/src/utils/github-prs.test.ts: P (exit 0)
round 2 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.review-lease.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.worktree.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/acp-http/transport.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/fast-path-open.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/routes/session-pr-backfill.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)

Evidence images

01-ab-head

02-ab-base

03-redos-original

04-redos-fixed

05-mutation-b1

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

Qwen Code · sandboxed verification

@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. ✅

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

APPROVE — 独立复验 @ 0a3bfcaa:最后的站立 Critical R27-1 已在精确 head 核实闭合

Reviewed independently at head 0a3bfcaa65414f7502cf68b6646bf04c9a124f9e。「线程回复 ≠ 修复」——以下全部按当前代码读码判定。

历史阻塞项(仅验证站立项,不重复追已收口的旧轮)

  • R27-1(round-27 CHANGES_REQUESTED 于 a9bc0e06 提出的唯一 Critical:fork 布局下 attested() 对 convention 占位恒假 → 来源提升永不落地、会话自身 PR 仍可被任意 capped writer 驱逐)——已修复于本 head。fix commit 0a3bfcaa65(「let the trusted fork-parent page attest a promotion candidate」,+96 行)给 attested() 精确加上 finding 草拟的第三合取支 pageMapTrusted && pageUrl !== undefined && canonical === canonicalSessionPrUrl(pageUrl)(session-pr-backfill.ts :834-840),并保持其仅在确认父仓库页可信时生效(pageMapTrusted 门与 :373-376 的注释纪律一致;pageUrlByNumber 在 repo 门之前录制、fork 布局下归因到父页 url,:360-367)。新增 86 行测试同时钉住正臂(source:'review' → 提升为 'worktree',written:1)与负臂(发散页——attribution 未确认父仓——绝不 attest、条目原样不动);移除新合取支即打红,符合该轮的 fix-witness 要求。
  • round-26 的两条 Critical(backfill:508、session-pr-service:423)在 a9bc0e06 收口:round-27 全量重测时未再列站立案,其评审体仅新开 R27-1 一条;本轮读 head 亦确认两处修复形态在位(:505-520 的 legacy-fabricated 全路径比对等)。
  • round-27 deferred 清单的 12 条均为 Suggestion 级测试见证/死代码类,按流程不构成门禁,留作后续。

当前扫描(Critical-only)

除上述 fix commit 逐行读过外,复核了本 head 的权限序与驱逐方向:sessionPrSourceAuthority worktree 3 > create 2 > 无来源 1 > review 0,capSessionPrListByAuthority 升序先逐出最弱、同级保持原位——与文档「created/convention bindings 优先存活」一致,无翻转错误。未见可证明的新阻塞缺陷。

CI(非门禁,仅陈述)

本 head 24 success / 37 skip / 1 failure = review-pr(评审工作流自身状态,按规则不构成卡点);无失败证据指向本 PR 引入的阻塞性缺陷。

✅ APPROVE — 最后站立的历史 Critical 已在精确 head 读码闭合,当前扫描干净;批准。

— 衍星 · read-only PR review (posted as qqqys)

@wenshao
wenshao added this pull request to the merge queue Sep 2, 2026
Merged via the queue into QwenLM:main with commit 95cdb5d Sep 2, 2026
80 of 81 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants