Skip to content

fix(daemon): Cancel timed-out session initialization - #10268

Open
doudouOUC wants to merge 6 commits into
QwenLM:mainfrom
doudouOUC:fix/session-initialization-deadline
Open

fix(daemon): Cancel timed-out session initialization#10268
doudouOUC wants to merge 6 commits into
QwenLM:mainfrom
doudouOUC:fix/session-initialization-deadline

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR makes the existing daemon session initialization budget authoritative end to end. The bridge sends a private absolute deadline with each managed ACP new-session request, the child propagates cancellation through configuration, Gemini startup, and SessionStart hooks, and a timed-out Session is rejected before publication. For older children that ignore the deadline, the bridge tracks the late request, closes any late-created Session by exact ID, retains admission and caller-supplied ID fences until cleanup settles, and quarantines only fresh session creation when cleanup remains uncertain. Healthy sibling Sessions on a shared channel remain usable.

Why it's needed

The public newSession timeout previously rejected only the bridge wrapper while the underlying child work could continue. A slow SessionStart command hook could therefore finish after the caller received init_timeout, leave descendant processes running, and publish a real child Session that the bridge never registered. Killing the shared ACP channel at the first timeout would avoid hidden work but would also terminate unrelated healthy Sessions, so the timeout needs request-scoped cancellation plus a bounded compatibility lifecycle for older children.

Reviewer Test Plan

How to verify

On a managed shared ACP channel, keep one healthy Session alive and create a second Session whose SessionStart command hook exceeds the initialization budget. Expect the second request to fail with the existing init_timeout contract, never publish a Session, and reclaim both the hook root and descendant, while the healthy sibling still completes a prompt. With a compatibility child that ignores the private deadline, resolve newSession after the public timeout and confirm the bridge sends exactly one close request for the returned Session ID. Make the close return closed: false, and separately leave the raw request unsettled for one further budget; in both cases expect fresh admission to fail with the documented acp_channel_unavailable reason while the sibling remains usable.

Evidence (Before & After)

Before: the caller received init_timeout, but an older or non-cancelling child could finish initialization later, publish an unregistered Session, and leave a SessionStart hook process tree running.

After: managed children abort before Session publication and tree-aware Hook cancellation reclaims the root and descendant. Older children are contained by exact-ID late cleanup; uncertain cleanup fails closed only for fresh admission and does not terminate healthy siblings.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

macOS 26.4.1 (Darwin 25.4.0 arm64), Node.js 22.22.3, npm 10.9.8. The full ACP Bridge test file passed 809 tests, the ACP Agent test file passed 511 tests, and the Core client plus HookRunner files passed 424 tests. The affected ACP Bridge and Core packages passed build and typecheck; changed files passed ESLint, Prettier, and git diff --check. A real HookRunner process-tree harness confirmed that an abort reaped both the hook root and a descendant that ignored SIGTERM. The repository-wide npm run build && npm run bundle remains blocked before bundling by the current main branch's unrelated Ink selection API/type mismatch in CLI UI files.

Risk & Scope

  • Main risk or tradeoff: Timer ordering, late ACP settlement, and channel draining now share one lifecycle; uncertainty intentionally refuses new Sessions on that channel until it drains instead of risking hidden state or killing healthy siblings.
  • Not validated / out of scope: A full built-daemon E2E could not run because of the unrelated current-main Ink build failure. Real Windows and Linux execution, load/resume deadlines, prompt cancellation, endpoint-specific deadlines, and OS-level cgroup or Job Object containment remain out of scope.
  • Breaking changes / migration notes: No intended breaking change. The public timeout remains init_timeout; the private deadline is restricted to the trusted managed-parent path. Two additive acp_channel_unavailable reason values are documented for clients that inspect quarantine details.

Linked Issues

Closes #10266

中文说明

本 PR 做了什么

本 PR 让现有 daemon Session 初始化预算在整条链路上具备权威性。Bridge 会在每个受管 ACP 新建 Session 请求中发送私有的绝对 deadline,子进程将取消信号传递到配置初始化、Gemini 启动和 SessionStart Hook,并在发布前拒绝已经超时的 Session。对于忽略 deadline 的旧子进程,Bridge 会跟踪迟到请求,按精确 Session ID 关闭迟到创建的 Session,在清理完成前保留准入和调用方指定 ID 的 fence,并在无法确认清理结果时只隔离新的 Session 创建。共享 channel 上的健康兄弟 Session 仍然可用。

为什么需要

此前公开的 newSession 超时只会拒绝 Bridge 包装层,底层子进程工作仍可能继续。缓慢的 SessionStart 命令 Hook 可能在调用方已经收到 init_timeout 后才完成,留下仍在运行的后代进程,并发布一个 Bridge 从未注册的真实子 Session。首次超时时直接终止共享 ACP channel 虽然可以避免隐藏任务,但也会终止无关的健康 Session,因此这里需要请求级取消,以及面向旧子进程的有界兼容生命周期。

Reviewer 测试计划

如何验证

在受管共享 ACP channel 上保留一个健康 Session,并创建第二个 Session,使它的 SessionStart 命令 Hook 超过初始化预算。预期第二个请求以现有 init_timeout 契约失败、始终不发布 Session,并回收 Hook 根进程和后代进程,同时健康兄弟仍可完成一次 prompt。再使用忽略私有 deadline 的兼容子进程,在公开超时后让 newSession 返回,并确认 Bridge 仅针对返回的 Session ID 发送一次精确 close 请求。让 close 返回 closed: false,并单独让原始请求再经过一个完整预算仍不 settle;两种情况下都应看到新准入以文档化的 acp_channel_unavailable reason 失败,而兄弟 Session 仍然可用。

证据(前后对比)

改动前:调用方会收到 init_timeout,但旧版或无法取消的子进程仍可能稍后完成初始化,发布一个未注册的 Session,并留下仍在运行的 SessionStart Hook 进程树。

改动后:受管子进程会在 Session 发布前中止,具备进程树感知能力的 Hook 取消会回收根进程和后代。旧子进程通过精确 ID 的迟到清理进行约束;清理结果不确定时只对新准入失败关闭,不会终止健康兄弟。

测试平台

操作系统 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

环境(可选)

macOS 26.4.1(Darwin 25.4.0 arm64)、Node.js 22.22.3、npm 10.9.8。完整 ACP Bridge 测试文件通过 809 个测试,ACP Agent 测试文件通过 511 个测试,Core client 与 HookRunner 文件通过 424 个测试。受影响的 ACP Bridge 和 Core 包通过 build 与 typecheck;改动文件通过 ESLint、Prettier 和 git diff --check。真实 HookRunner 进程树测试确认 abort 会回收 Hook 根进程,以及一个忽略 SIGTERM 的后代进程。全仓库 npm run build && npm run bundle 仍在 bundle 前被当前 main 分支中与本改动无关的 CLI UI Ink selection API/类型不匹配阻断。

风险与范围

  • 主要风险或权衡:定时器顺序、迟到 ACP settle 和 channel drain 现在共享同一生命周期;当状态不确定时,会有意拒绝该 channel 上的新 Session,直到 channel 完成 drain,从而避免隐藏状态,也避免终止健康兄弟。
  • 未验证或范围外:由于当前 main 上无关的 Ink build 失败,未能运行完整 built-daemon E2E。真实 Windows 和 Linux 执行、load/resume deadline、prompt 取消、端点级 deadline,以及操作系统级 cgroup 或 Job Object containment 均不在范围内。
  • 破坏性变更或迁移说明:没有预期的破坏性变更。公开超时仍为 init_timeout;私有 deadline 仅限可信受管父进程路径。针对检查隔离细节的客户端,文档新增了两个附加的 acp_channel_unavailable reason 值。

关联 Issue

Closes #10266

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

E2E test report

Verified on rebased commit 70aaa0d697c0a38ff322a9d3f8ad2f6b2cd961c2 on macOS 26.4.1 with Node.js 22.22.3.

  • Bridge compatibility scenarios passed: late success triggered one exact-ID qwen/control/session/close; the hidden Session was never registered; capacity and caller-supplied ID fences remained until settlement; closed: false and settlement overdue blocked fresh admission without killing the healthy sibling.
  • Managed ACP Agent scenarios passed: a trusted deadline aborted initialization before Session publication, while an untrusted caller could not forge the private deadline.
  • Core signal propagation passed: the request signal reached the SessionStart hook and cancellation was not swallowed as an ordinary best-effort hook error.
  • A real HookRunner process-tree harness passed: abort reclaimed both the hook root and a descendant that ignored SIGTERM.
  • Focused and full affected-file results: ACP Bridge 809 passed; ACP Agent 511 passed; Core client and HookRunner 424 passed; ACP Bridge/Core build and typecheck passed; changed-file ESLint, Prettier, and git diff --check passed.

The complete built-daemon scenario could not run because npm run build && npm run bundle is blocked in unchanged CLI UI files by the current main branch's Ink selection API/type mismatch (selectable, selectionFlow, getFrameController, ReadonlyFrame, and related types). No changed file appears in that failure set.

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Aug 27, 2026
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 27, 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 27, 2026

Copy link
Copy Markdown
Collaborator

Gate re-pass on head 23a2637d — round 3. Delta since the last gate pass: the R4-1 public init-timeout mapping (both transports) and a rebase onto current main (Gemini→Llm rename adaptations). Moving on to code review. 🔍

Template: complete ✓

Problem: unchanged and real — an observed bug, not hardening. The public newSession timeout rejected only the bridge wrapper while the child kept working; issue #10266 is open, self-reported, and carries a reproduction. Verified against the base code in round 1, not taken on faith.

Direction: aligned. This closes a genuine daemon correctness hole (a slow SessionStart hook publishing a session the bridge never registered, with orphaned hook processes) while keeping the public init_timeout contract intact. Not touching it would leave hidden sessions on every shared channel where an init times out.

Size: core + cross-package change (packages/core/src/config, packages/core/src/core/client, acp-bridge, cli/serve, cli/acp-integration). 731 production logic lines (bridge.ts 517, acpAgent.ts 75, client.ts 40, config.ts 25, dispatch.ts 16, error-response.ts 13, smaller files 45) vs 1006 test lines vs 97 docs lines. This is a fix, so no hard block — but 500+ production lines in core triggers the maintainer-awareness escalation, which caps the Stage 3 verdict regardless of how clean the review is.

Approach: scope is justified rather than bloated. Child-side cancellation alone cannot cover older children that ignore the deadline, so the bridge's late-settlement containment (exact-ID close, fences, bounded quarantine) is part of the stated goal, not scope creep. The design doc and protocol-doc updates match the code. Nothing I would cut.

Risk: Stage 1e matches packages/cli/src/acp-integration/ (a revert-correlated high-risk path). Consequence: full Stage 2 depth and CI evidence before any verdict. The author lacks write access, so the sandboxed lane runs as a sponsored /verify (see Stage 2).

中文说明

在 head 23a2637d 上门禁复查 —— 第 3 轮。距上次门禁通过的增量:R4-1 公开超时映射(两个传输层)以及 rebase 到最新 main(Gemini→Llm 重命名适配)。进入代码审查 🔍

模板: 完整 ✓

问题: 不变且真实 —— 已观测到的 bug,不是理论加固。公开的 newSession 超时只拒绝 Bridge 包装层,子进程继续工作;issue #10266 开启中、作者自报且带复现。第 1 轮已在 base 代码上验证,不是凭空采信。

方向: 对齐。修补真实的 daemon 正确性漏洞(缓慢的 SessionStart hook 发布一个 Bridge 从未注册的 session,并留下孤儿进程),同时保持公开 init_timeout 契约不变。不修的话,任何共享 channel 上的初始化超时都会留下隐藏 session。

规模: 核心 + 跨包改动。731 行生产逻辑(bridge.ts 517、acpAgent.ts 75、client.ts 40、config.ts 25、dispatch.ts 16、error-response.ts 13、其余小文件 45),测试 1006 行,文档 97 行。类型为 fix,无硬性拦截 —— 但核心路径 500+ 生产行触发维护者知悉升级,Stage 3 结论将受该策略封顶。

方案: 范围合理而非膨胀。仅靠子进程侧取消无法覆盖忽略 deadline 的旧子进程,因此 Bridge 的迟到 settle 约束(精确 ID 关闭、fence、有界隔离)是目标的一部分,不是夹带。设计文档与协议文档更新与代码一致。没有可砍的部分。

风险: Stage 1e 命中 packages/cli/src/acp-integration/(与 revert 相关的高风险路径)。因此需要完整 Stage 2 深度与 CI 证据。作者无写权限,沙箱验证以 sponsored /verify 形式进行(见 Stage 2)。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review — round 3 (head 23a2637d)

Round-2's head got three more pushes: the R4-1 public init-timeout mapping, a Critical-fix batch (per-token overdue tracking, exact-ID ownership reservations, protocol-table retry hints), and a rebase onto main. I re-read the full production diff at this head and verified the prior Criticals against the code, not the author's tables.

The ordering hazard this round's headline fix introduces is handled correctly. SessionRestoreTimeoutError extends BridgeTimeoutError, so a naive matcher would silently remap restore timeouts as init_timeout. Both transports match the subclass first: sendBridgeError checks instanceof SessionRestoreTimeoutError before instanceof BridgeTimeoutError (error-response.ts:200 vs :217), and toRpcError switches on 'SessionRestoreTimeoutError' before 'BridgeTimeoutError' (dispatch.ts:824 vs :841) with the subclass setting its own name. The mapping is also consistent with the existing taxonomy — mapDomainErrorToErrorKind already maps any BridgeTimeoutError to init_timeout.

Prior Criticals verified fixed at this head:

  • The drain-path predicate regression (round-1 R1-11 class): every condemnation read now goes through channelIsCondemned() — active-work hold, bounded agent close, and idle-close all four states instead of only the restore-era flags.
  • The channel-wide overdue boolean is gone — replaced by the per-token overdueAbandonedNewSessions set; I independently grepped the head file: 0 occurrences of the old flag, and the settle finally removes per token.
  • Late-cleanup ID collisions: settleAbandonedNewSession awaits a rival restore/spawn's settlement promise before reserving exact-ID cleanup, and skips any id a live session owns — an abandoned request's own reservation can't deadlock the loop because the lateSessionId === requestedSessionId branch breaks instead of awaiting itself.
  • awaiting_abandoned_cleanup retry hints now follow the timed-out operation's budget (init budget for a caller-supplied-id spawn, restore budget for a restore) on both the 409 fence and the 503 quarantine, and the stale blanket-504 doc wording is corrected.

Trusted-path gating is sound. The deadline is read only behind the managed-parent capability handshake; malformed trusted values fail with invalid_session_initialization_deadline before any settings/session state is created; untrusted callers' deadline metadata is ignored (all three covered by new agent tests). The signal is request-scoped, unreferenced from timers, disposed in finally, and never stored on the published Session.

No new Criticals this round. Remaining items are non-blocking and disclosed: R4-2/R4-3 (mutation-strengthening of the overdue and recycle tests) were deferred by the author under the review-churn rule; the round-5 /review suggestion R5-1 (route-level pin for the new 504 contract) stands as a suggestion; the beyond the timer range real-clock flake was previously accepted as non-blocking.

sequenceDiagram
    participant P1 as Caller
    participant P2 as Bridge
    participant P3 as ACP child
    P1->>P2: newSession
    P2->>P3: newSession with absolute deadline in _meta
    Note over P2: public budget expires first
    P2-->>P1: init_timeout, abandoned token tracked
    P3->>P2: late success with sessionId
    P2->>P3: one bounded close by exact id
    alt closed true or resource not found
        P2->>P2: settle, release capacity and id fences
    else closed false, or unsettled one further budget
        P2->>P2: refuse only fresh sessions on the channel, siblings stay usable
    end
Loading

Test evidence — the PR's own CI at the reviewed commit

Unattended run: I did not build or execute any PR code; the evidence below is the PR's own CI fetched via the API for 23a2637d. Everything settled — no pending checks. Integration Tests (CLI, No Sandbox) and the macOS/Windows unit matrix were skipped (fork-PR gating, not a failure); the author's local macOS numbers (816 bridge tests etc.) are their claim, not independently re-run here.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Real daemon E2E / Java 11 success
Serve A/B (ubuntu-latest, Node 22.x) success
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
ubuntu-latest / Java 11, 17, 21 · macos-latest / Java 21 · windows-latest / Java 21 (SDK Java) success
Dependency CVE audit · Secret scan (TruffleHog) success
Integration Tests (CLI, No Sandbox) skipped (fork gating)
Test (macos-latest, Node 22.x) · Test (windows-latest, Node 22.x) skipped (fork gating)

The central claim is behavioural and the green unit suite alone does not pin it (a suite can pass with the containment removed). Sandboxed verification would settle the rest: @qwen-code /verify — that a timed-out init really reaps the hook process tree, that an older child settling late is closed by exact ID without touching a healthy sibling, and that cleanup-uncertainty fails closed only for fresh admission. The author lacks write access, so this is a sponsored run (a maintainer's @qwen-code /verify approves the head it was written against; the run carries a pre-execution risk screen and a full workspace wipe — read its report with the same skepticism as the fork's own CI logs). A verification run is already in flight on this head (workflow run); its report lands in this thread. Prior /verify rounds on earlier heads passed (4746 then 4824 scripted assertions, 0 failures).

中文说明

代码审查 —— 第 3 轮(head 23a2637d

第 2 轮的 head 之后又有三次推送:R4-1 公开超时映射、一批 Critical 修复(按 token 的 overdue 跟踪、精确 ID 所有权预留、协议表重试提示)、以及 rebase 到 main。我在该 head 上重新通读了全部生产代码 diff,并对照代码(而非作者的表格)验证了此前的 Critical。

本轮核心修复引入的顺序风险处理正确。 SessionRestoreTimeoutError extends BridgeTimeoutError,粗心的匹配会把 restore 超时静默重映射为 init_timeout。两个传输层都先匹配子类:sendBridgeError 先判 SessionRestoreTimeoutError(error-response.ts:200 对 :217),toRpcError 的 switch 先命中 'SessionRestoreTimeoutError'(dispatch.ts:824 对 :841),且子类设置了独立 name。该映射也与现有错误分类一致 —— mapDomainErrorToErrorKind 本就把任何 BridgeTimeoutError 映射为 init_timeout

此前的 Critical 已验证在该 head 修复: drain 路径判定回归(R1-11 类)全部改走 channelIsCondemned();channel 级 overdue 布尔量已被按 token 的 overdueAbandonedNewSessions 集合取代(我独立 grep 了 head 文件:旧标志 0 次出现);迟到清理的 ID 冲突会先等待在途 restore/spawn 的 settlement,且跳过已被活跃 session 拥有的 id,不会自锁;awaiting_abandoned_cleanup 的重试提示在 409 与 503 上都跟随超时操作的预算。

可信路径门控正确。 deadline 仅在受管父进程能力握手后读取;受信任但格式非法的值在创建任何状态前即以 invalid_session_initialization_deadline 失败;不受信调用方的 deadline 元数据被忽略(均有新测试覆盖)。信号是请求级的、定时器全部 unref、finally 中清理、不保存到已发布的 Session。

本轮无新 Critical。 其余均为已披露的非阻塞项:R4-2/R4-3 由作者按评审变更规则延期;第 5 轮 /review 的 R5-1(504 契约的 route 级钉桩)为建议级;beyond the timer range 时钟抖动 flake 此前已被接受为非阻塞。

时序图见英文部分(调用方 → Bridge → ACP 子进程的超时后迟到结算与有界清理路径)。

测试证据 —— 该 commit 上 PR 自身的 CI

无人值守运行:未构建或执行任何 PR 代码,以下证据来自 API 拉取的 23a2637d 的 PR 自身 CI,且全部出结果(无 pending)。Integration Tests (CLI, No Sandbox) 与 macOS/Windows 单元测试矩阵为 skipped(fork PR 门禁,非失败);作者的本地 macOS 数字是其自述,未在此独立复跑。CI 表格见英文部分(机器可读区域内)。

核心主张是行为性的,仅凭绿色单测无法钉桩。沙箱验证可补齐:@qwen-code /verify —— 验证超时初始化确实回收 hook 进程树、旧子进程迟到 settle 被按精确 ID 关闭且不伤及健康兄弟、清理不确定时仅对新准入失败关闭。作者无写权限,因此为 sponsored run(由维护者的 @qwen-code /verify 批准,运行前有安全筛查与完整工作区清理 —— 请像对待 fork CI 日志一样审慎阅读其报告)。当前已有一个针对该 head 的验证运行在进行中,报告将发布在本线程;此前两轮 /verify(在更早 head 上)分别以 4746、4824 条断言全部通过。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review at this head; the 3 is pure policy, not doubt. The Stage 0 core-size escalation (731 production lines across acp-bridge/core/cli) caps this PR and withholds bot auto-approval — and the maintainer sign-off that cap asks for has now arrived: @yiliang114 approved this exact commit after an independent verification pass.

Honest read after three rounds: this PR has earned its approvals. The problem is observed, not hypothesized — a timed-out newSession really does leave the child initializing, and the reproduction is in the linked issue. The approach is the minimal one that covers the stated goal: request-scoped cancellation for children that honor the deadline, plus a bounded late-settlement containment for children that don't, because either half alone leaves the hole open. The five review rounds produced structural fixes, not churn — the drain-path predicates, the per-token overdue tracking, the exact-ID ownership reservations, and this round's transport mapping are each real defect classes closed. If I'm maintaining this in six months the lifecycle is intricate, but the intricacy is in the problem (timer ordering, late settlement, and drain share one state machine), it's documented in the design and protocol docs, and the tests genuinely pin the containment — several fail with the guards removed.

What my own pass added this round: a full re-read of the production diff at 23a2637d, an independent grep confirming the old channel-wide overdue flag is gone, and a deliberate probe of the one hazard the new init_timeout mapping introduces (SessionRestoreTimeoutError extends BridgeTimeoutError) — both transports match the subclass first, so restore timeouts cannot be remapped. No new Criticals found. The /review round at this head likewise landed at Suggestion-level only.

Why I'm still not approving: policy, stated plainly. A fork PR carrying 500+ production lines into core paths is one the bot does not auto-approve regardless of how clean every stage looks — that is the escalation working as designed, and nothing about this round changes it. CI is fully settled and green at the reviewed commit, so there is no pending-work reason to wait either; the only remaining step is human.

⏸️ Deferring to @yiliang114 — the cap is policy, the code is ready by my read, and your approval already stands on this exact commit. What's left is a human call: main needs a second approval, and the on-record CHANGES_REQUESTED state is stale (this bot's own round-4 request-changes sits on superseded head 9bfba010, replaced by its round-5 pass here; @wenshao's dates to e8c9417) — dismiss those if they would block the merge. A sandboxed /verify run on this head is still in flight and will report in-thread; earlier heads passed it cleanly.

中文说明

置信度:3/5 —— 该 head 上审查干净;3 分纯属策略封顶,而非存疑。Stage 0 核心规模升级(731 行生产代码横跨 acp-bridge/core/cli)封顶本 PR 并禁止机器人自动批准 —— 而该封顶所要求的维护者签核已经到位:@yiliang114 在独立验证后批准了这个确切的 commit。

三轮下来的诚实判断:这个 PR 配得上它的批准。问题是观测到的而非假设 —— 超时的 newSession 确实会留下仍在初始化的子进程,复现在关联 issue 中。方案是覆盖目标的最小方案:对遵守 deadline 的子进程做请求级取消,对不遵守的做有界的迟到结算约束,二者缺一都会留下漏洞。五轮评审产出的是结构性修复而非变更噪音 —— drain 路径判定、按 token 的 overdue 跟踪、精确 ID 所有权预留、以及本轮的传输层映射,每一类都是真实缺陷的关闭。六个月后维护它时,生命周期固然复杂,但复杂源于问题本身(定时器顺序、迟到结算、drain 共享同一状态机),且有设计文档与协议文档支撑,测试也真正钉住了约束 —— 移除守卫后若干测试会失败。

本轮我自己补充的:在 23a2637d 上完整重读生产 diff,独立 grep 确认旧的 channel 级 overdue 标志已移除,并刻意探测了新 init_timeout 映射引入的唯一风险(SessionRestoreTimeoutError extends BridgeTimeoutError)—— 两个传输层都先匹配子类,restore 超时不会被错误重映射。未发现新 Critical;该 head 上的 /review 一轮也只剩建议级。

为何仍不批准:策略,直说。携带 500+ 生产行核心路径改动的 fork PR,无论各阶段多干净,机器人都不自动批准 —— 这正是升级机制的设计意图,本轮没有任何因素改变它。CI 在该 commit 上已全部出结果且全绿,也不存在等待未决检查的理由;唯一剩下的步骤是人工。

⏸️ 转交 @yiliang114 —— 封顶是策略,代码以我的判断已经就绪,你的批准已经落在该 commit 上。剩下的是人工决定:main 需要第二个批准;记录在案的 CHANGES_REQUESTED 已过时(本机器人第 4 轮的 request-changes 停留在已被取代的 head 9bfba010 上,已被其第 5 轮取代;@wenshao 的停留在 e8c9417)—— 如果它们会阻塞合并,请 dismiss。针对该 head 的沙箱 /verify 运行仍在进行中,报告将发布在本线程;更早的 head 已通过该验证。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

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

@qqqys

qqqys commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

tmux E2E test report (head 70aaa0d697)

Independent review of the production diff found no blocking (Critical) issues; per review policy, here is the functional verification pass. Scope: the end-to-end session-initialization deadline (bridge-side late-settlement containment + child-side cancellation propagation).

Static review of the high-risk surfaces.

  • Bridge late-settlement containment: a timed-out newSession whose ACP call is still live is tracked (unsettledAbandonedNewSessions), and a late-created session is closed by exact ID with a byId.has(lateSessionId) reclaim guard first — a late id already owned by a live session is skipped, never closed. Abandoned settlements count against maxSessions at all three admission sites, admission release is deferred until settlement completes, and a failed cleanup quarantines the channel fail-closed (new_session_cleanup_failed / new_session_settlement_overdue → 503 with backoff). All new timers are unref()'d and cleared on channel teardown and in the settlement finally.
  • Child-side cancellation: the deadline is honored only from a trusted managed parent (isTrustedManagedParent() guard — an untrusted caller cannot force a premature cancel), validated as a positive safe integer within timer range, and the AbortSignal is threaded through config setup, auth, filesystem setup, session registration, Gemini initialize(), and tool registration with throwIfAborted() between steps; the timer is disposed in finally.

Functional verification at this head (tmux). Fetched the PR head into a scratch tree and ran the new settlement tests in a tmux pane:

  • bridge.test.ts -t 'abandoned newSession': 2/2 pass ("holds a requested id until an abandoned newSession settles", "refuses fresh sessions when an abandoned newSession does not settle"); the run's stderr shows the live mechanism firing — newSession timed out … decision=fence_shared and abandoned newSession has not settled … refusing fresh sessions … until it drains.

Honest limits. This verifies the bridge settlement core; the full child-side cancellation path (config/Gemini/SessionStart-hook propagation in acpAgent.ts/client.ts) was reviewed statically but its dedicated suites (acpAgent/client) were not re-run here, and the full CI suite is still in flight on this head. Not approving in this pass: the bot deferred at 3/5 for maintainer sign-off on the core-change size and CI has not landed green.


tmux E2E 测试报告(head 70aaa0d697

对生产代码 diff 的独立审查未发现阻塞性(Critical)问题;按评审规则,这里给出功能性验证。范围:端到端会话初始化截止时间(bridge 侧迟到结算收容 + 子进程侧取消传播)。

高风险面静态审查。

  • Bridge 迟到结算收容:超时但 ACP 调用仍存活的 newSession 被跟踪(unsettledAbandonedNewSessions),迟到创建的会话按精确 ID 关闭,且先经 byId.has(lateSessionId) 回收守卫——已被存活会话拥有的迟到 id 会被跳过、绝不关闭。被弃结算在三处准入口都计入 maxSessions,准入释放推迟到结算完成,清理失败则失败即关闭地隔离通道(new_session_cleanup_failed / new_session_settlement_overdue → 503 带退避)。所有新计时器均 unref() 并在通道拆除与结算 finally 中清理。
  • 子进程侧取消:截止时间仅受信受管父进程才会被采纳(isTrustedManagedParent() 守卫——非受信调用方无法强制提前取消),校验为定时器范围内的正安全整数,AbortSignal 贯穿 config 建立、鉴权、文件系统建立、会话注册、Gemini initialize() 与工具注册,各步骤间 throwIfAborted();计时器在 finally 中销毁。

本 head 上的功能验证(tmux)。 将 PR head 拉入临时目录,在 tmux 面板中运行新的结算测试:

  • bridge.test.ts -t 'abandoned newSession'2/2 通过("holds a requested id until an abandoned newSession settles"、"refuses fresh sessions when an abandoned newSession does not settle");运行的 stderr 显示机制真实触发——newSession timed out … decision=fence_sharedabandoned newSession has not settled … refusing fresh sessions … until it drains

如实说明的局限。 本验证覆盖 bridge 结算核心;完整的子进程侧取消路径(acpAgent.ts/client.ts 中 config/Gemini/SessionStart 钩子传播)经静态审查,但其专属套件(acpAgent/client)未在此重跑,且完整 CI 套件仍在本 head 上运行。本轮不予 approve:机器人以 3/5 因核心改动规模上报维护者签核而推迟,且 CI 尚未落绿。

@doudouOUC doudouOUC self-assigned this Aug 27, 2026
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

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

Scripted assertions: 4746 passed · 0 failed · 4746 total

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

中文 — 判定:✅ 通过 · 可合入(agent 判定)

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

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

抖动门:✅ 5 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 10268 Deep Verification Report

Verdict: merge-ready — 4746 scripted assertions executed, 0 failed.
Verified head: 70aaa0d697c0a38ff322a9d3f8ad2f6b2cd961c2 (HEAD^2), base tip 4e9c2e87db65a8e7c890bb26893390eff7ca1787 (HEAD^1, == origin/main in this checkout).

中文摘要
  • 结论merge-ready(断言总数与通过数见文首行及 assertions.json)。
  • A/B 结论(核心主张:初始化超时后对迟到 newSession 的收口):在真实 NDJSON/ACP wire 上、使用编译产物(head dist vs base dist)驱动 createAcpSessionBridge。逐单元对比见 "Central claim and A/B proof" 表:base 侧 0 个 close 请求、迟到会话在 child 上保持打开(隐藏会话,即 Daemon session initialization continues after timeout #10266 的 bug)、超时后新准入照常放行;head 侧携带私有 deadline meta、迟到会话被精确 ID 的 sessionClose 恰好关闭一次、清理被拒/永不 settle 时按文档化 reason 隔离、健康兄弟全程可用。证据图:01-ab-head-late-newsession-contained.png02-ab-base-hidden-session-leaks.png
  • Hook 进程树:真实进程实验(A/A 两臂一致 —— 树回收机制来自 base 的 fix(core): Reclaim command hook process trees #10100,本 PR 未改 hooks/,只新增信号接线):abort 后 hook 根进程与忽略 SIGTERM 的后代进程均被回收。见 "Hook process-tree reclamation" 表与 04-hook-tree-reclaim-head.png
  • 空泛性检验:6 个变异体(含一次整组回退)全部被 PR 新增测试以行为性断言捕获,0 存活。见 "Mutation matrix" 表与 03-mutation-matrix-zero-survivors.png
  • 测试门:四个受影响范围的套件/文件全部通过(1 个既有环境性 skip),明细见 "Targeted gates" 表。
  • Findings:无阻塞项。1 条建议(受托父级发来畸形 deadline 的 invalidParams 分支无测试覆盖,属完整性缺口);1 条对描述的更正("main 上 bundle 被 Ink 问题阻断"在本验证合并提交上不可复现)。见 "Corrections" 与 "Findings" 节。
  • 未覆盖:真实 CLI child 的完整 daemon E2E、Windows、load/resume 期限、prompt 取消、OS 级 cgroup/Job Object 限制、全仓测试套件(由 PR CI 覆盖)、逐提交归因(depth-2 检出)。详见 "Not covered" 节。

Central claim and A/B proof

Central claim: when a managed ACP newSession exceeds the init budget, the caller gets
the existing init_timeout contract AND the underlying child work is contained — a
late-created session is closed by exact ID (never left published-but-unregistered),
quarantine is fail-closed but reason-scoped when cleanup is uncertain, and healthy
sibling sessions remain usable.

Harness: ab-late-newsession.mjs drives the compiled dist of each arm's
createAcpSessionBridge against a scripted fake ACP peer over real NDJSON streams
(AgentSideConnection from @agentclientprotocol/sdk) — the same wire design as the
package's own test fixtures; no code under test is mocked. Both arms ran the identical
stimuli; expectations are per-arm (base cells assert the presence of the bug).

Cell (initializeTimeoutMs in parens) base (4e9c2e87db) head (70aaa0d697)
Deadline meta on the wire (S1/S4) absent present (qwen.daemon.sessionInitializationDeadlineMs, epoch ms)
Public result of the slow spawn (S1–S3) BridgeTimeoutError(newSession) at 300/200ms identical (contract preserved)
sessionClose requests for the late session (S1, 300ms) 0 exactly 1, exact ID late-hidden
Late session state on the child after settle (S1) OPEN (open=[sibling-1,visible-3,late-hidden]) — the hidden session closed
Spawn while settlement pending, maxSessions=2 (S1) succeeds (nothing tracked) SessionLimitExceededError (settlement holds a slot), succeeds after settle
Close refused → fresh admission (S2) allowed (child state unknown) quarantined, reason new_session_cleanup_failed
Request never settles → fresh admission after one further budget (S3) allowed quarantined, reason new_session_settlement_overdue
Sibling prompt during/after all of the above end_turn in every cell end_turn in every cell
Channel killed? (S2/S3) no no (siblings protected)
Shutdown with a hung settlement (S3) completes completes ≤5s (channel teardown settles the abandoned request via channelUnavailableReject)
Cooperative child enforcing the deadline (S4, 1000ms budget) raw RPC error {code:-32603} passes through mapped to BridgeTimeoutError in 30ms

Head run: 26/26 scripted assertions; base run: 25/25 (base-side cells assert the bug).
Witnesses: 01-ab-head-late-newsession-contained.png, 02-ab-base-hidden-session-leaks.png.

Empty-channel branch (ab-empty-channel.mjs, the decision=kill_empty path): on head the
empty channel is recycled exactly as on base (channel exited, 0 session(s) torn down), the late
settle takes the transport_closed branch (child already dead — no close needed, no
markSessionClosed, no quarantine), the late session is never registered in the bridge, and the
next spawn is served fresh. Head 5/5, base 3/3. My first version of the head assertion failed
because the in-process fake "survives" its own transport close (a real child process would be
dead); the corrected assertion targets the production invariant and the discarded reading is
reported here rather than buried.

Corrections

  • Correction to the PR description ("the repository-wide npm run build && npm run bundle
    remains blocked before bundling by the current main branch's unrelated Ink selection
    API/type mismatch"): not reproducible at the verified merge commit. npm run bundle
    exits 0 here (log: bundle-head.log, "✅ All bundle assets copied to dist/"). Since this
    PR touches no CLI UI / Ink files and base == origin/main in this checkout, the claim
    likely describes a different main state than the one verified. This is a statement about
    the description, not a request to change code.

Findings

None blocking.

  1. (Suggestion, completeness) The malformed-deadline branches of
    createSessionInitializationDeadline (non-number, non-safe-integer, <= 0, or remaining
    > 2^31-1 from a trusted parent → invalidParams) are not pinned by any test; the new
    agent tests cover only the happy path and the untrusted-forged path. The code reads correct
    and the bridge-side analogue (initializeTimeoutMs NaN/Infinity/overflow) is tested, but a
    regression here would only surface as a trusted-parent RPC failure. Completeness reporting,
    not a merge condition.
  2. (Observation from the mutation matrix) Deleting only the newSessionCleanupFailed flag
    (mutant M3) still fails closed: the sibling settlement-overdue guard fires first and the test
    catches the mutant via the documented reason value (new_session_cleanup_failed expected,
    new_session_settlement_overdue observed). So the reason contract is load-bearing even where
    admission blocking is redundantly defended — worth knowing if anyone later "simplifies" the
    reason enum.

Mutation matrix (vacuity) — zero survivors

All mutants applied by exact string replacement in scratch trees, run through the same vitest
invocation, and restored (git checkout + clean git status). Every control was green first;
every failure below is the intended behavioral assertion (quoted from logs), not an import break.
Witness: 03-mutation-matrix-zero-survivors.png.

Mutant Suite under test Result Failing assertion (behavioral mismatch)
Control (unmutated) 5 new bridge tests 5 green
M1 lifecycle wrapper reverted to plain withTimeout (set revert) 5 new bridge tests 5 red e.g. "promise resolved {sessionId:'visible-3'} instead of rejecting" (no slot accounting); no close observed; no quarantine
M2 errorKind→BridgeTimeoutError mapping removed 5 tests 1 red "keeps the public timeout contract…": expected BridgeTimeoutError, got {code:-32603}
M3 newSessionCleanupFailed = true removed 5 tests 1 red "refuses fresh sessions when late newSession close is refused": expected reason new_session_cleanup_failed
M4 deadline _meta key removed 5 tests 1 red "closes a session created after the public newSession deadline": expected undefined to deeply equal Any<Number>
Control (unmutated) new client.test.ts test 1 green
M5 signal?.throwIfAborted() removed from the hook-failure catch client.test.ts 1 red "passes cancellation to SessionStart hooks…": promise resolved undefined instead of rejecting (cancellation swallowed)
Control (unmutated) 2 new acpAgent tests 2 green
M6 isTrustedManagedParent() gate removed acpAgent.test.ts 1 red / 1 green "ignores a forged session initialization deadline from an untrusted parent": rejected with deadline-exceeded instead of resolving; trusted test still green (exact attribution)

Hook process-tree reclamation (real processes)

hook-tree-harness.mjs drives the compiled HookRunner with a real SessionStart command hook
(node descendant & sleep 30; the descendant traps and ignores SIGTERM), aborts the passed signal
at ~700ms, and asserts on live PIDs. Witness: 04-hook-tree-reclaim-head.png.

Check head base
Quick-hook control (harness alive) PASS PASS
Hook returns promptly with "Hook execution cancelled (aborted)" PASS (~2.0s escalation) PASS (~2.0s)
Root reclaimed PASS PASS
SIGTERM-immune descendant reclaimed (group SIGKILL) PASS PASS
Total 10/10 10/10

A/A by construction: git diff HEAD^1..HEAD -- packages/core/src/hooks is empty — the tree
reclamation machinery is #10100's (already in base); this PR's contribution is the wiring
(client → config → fireSessionStartEvent(signal)), which M5 above proves pinned. This is the
level that does not cross the workspace boundary into a full daemon E2E; see Not covered.

Targeted gates

Gate Result
packages/acp-bridge full suite (34 files) 1775/1775 passed (21.7s)
packages/core changed modules' files: client.test.ts + config.test.ts 947/947 passed
packages/core hook files (hookRunner, hook-runner.process, hookSystem, hookEventHandler) 296/296 passed
packages/cli changed files: acpAgent.test.ts + dispatch-error.test.ts + server.test.ts 1618 passed, 1 skipped (pre-existing skipIf(!hasIpv6Loopback) env skip)

Gate liveness: each package's gate was proven live by a red mutant run in the same file
(M1–M4 in bridge.test.ts, M5 in client.test.ts, M6 in acpAgent.test.ts) before the green
gate result was cited. Author-cited counts match where comparable: bridge.test.ts 809 ✓,
acpAgent.test.ts 511 ✓.

Not covered

  • Full daemon E2E with a real CLI child (the Reviewer Test Plan's exact scenario): not run.
    Substitute evidence chain: bridge behavior proven over the real ACP wire against a scripted
    peer that ignores the deadline (the "older child" contract); hook tree reclamation proven with
    real processes; the wiring between them pinned by mutation-verified unit tests. A real-child
    run would additionally cover only the spawn transport, which this PR does not modify.
  • Windows: hook tree code takes a different branch on win32 (detached: false,
    killDirectChild); this container is Linux. The PR itself marks Windows untested.
  • Load/resume deadlines, prompt cancellation, endpoint-specific deadlines, OS-level cgroup/Job
    Object containment (declared out of scope by the PR).
  • Repo-wide test suite, lint, typecheck: covered by the PR's own CI; not re-run here (the
    targeted gates above are the workspace-level equivalent for the changed surface). Bundle was
    run (see Corrections).
  • Per-commit attribution: the checkout is depth 2 (merge, base, head only), so the aggregate
    HEAD^1..HEAD diff is what was verified.
  • Malformed-deadline invalidParams branches (Finding 1) — reviewed by reading only.
  • The quarantine retryAfterSeconds hint semantics under real client retry loops (the dispatch
    mapping itself is tested by dispatch-error.test.ts and server.test.ts across all reasons).

Methodology

Environment: node v22.23.2 in the CI container, working tree = refs/pull/10268/merge
(depth 2); npm ci + npm run build completed at HEAD before this round. Base side: scratch
worktree at HEAD^1 with only core + acp-bridge rebuilt (scripts/build_package.js via
the repo tsc); the root node_modules was reused — valid because the PR changes no
package.json/lockfile — with the one internal link re-pointed: tmp/base-tree/node_modules/@qwen-code/qwen-code-core
symlinked into the base tree, asserted by resolution from inside the base dist
(import.meta.resolve('@qwen-code/qwen-code-core', …/base-tree/…/dist/bridge.js)
realpath tmp/base-tree/packages/core/dist/index.js) and by content
(SESSION_INITIALIZATION_DEADLINE_META_KEY: 0 occurrences in base dist, 2 in head). Nested
third-party node_modules (lockfile-identical) were symlinked from the head tree; a
tsconfig-relative @lydell/node-pty types path needed one more symlink — the only divergence
from a stock build, and it affects typing, not emitted code. Harnesses live in the artifact dir
(ab-late-newsession.mjs, ab-empty-channel.mjs, hook-tree-harness.mjs, descendant.mjs,
mutate.mjs, matrix-summary.mjs) and are rerunnable; raw per-run logs are alongside them
(head-run1.log, base-run1.log, empty-*.log, hook-*.log, vacuity-*.log,
gate-*.log, build-base-*.log, bundle-head.log). Evidence images in evidence/.

Flakiness gate log

rounds=5 files=5 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/acpAgent.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/acpAgent.test.ts
file packages/cli/src/serve/acp-http/dispatch-error.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/dispatch-error.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/core/src/core/client.test.ts: (cd packages/core) npx --no-install vitest run ./src/core/client.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridge.test.ts: PPPPP
  packages/cli/src/acp-integration/acpAgent.test.ts: PPPPP
  packages/cli/src/serve/acp-http/dispatch-error.test.ts: PPPPP
  packages/cli/src/serve/server.test.ts: PPPPP
  packages/core/src/core/client.test.ts: PPPPP

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

--- 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/acpAgent.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/core/src/core/client.test.ts: P (exit 0)
round 2 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/core/src/core/client.test.ts: P (exit 0)
round 3 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/core/src/core/client.test.ts: P (exit 0)
round 4 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 4 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 4 · packages/core/src/core/client.test.ts: P (exit 0)
round 5 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 5 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 5 · packages/core/src/core/client.test.ts: P (exit 0)

Evidence images

01-ab-head-late-newsession-contained

02-ab-base-hidden-session-leaks

03-mutation-matrix-zero-survivors

04-hook-tree-reclaim-head

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.

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.

中文说明

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

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

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

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/cli/src/serve/server.test.ts
Comment thread packages/core/src/config/config.ts
Comment thread docs/design/acp-session-initialization-deadline.md Outdated
Comment thread packages/acp-bridge/src/bridgeErrors.ts
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.test.ts Outdated
doudouOUC added a commit to doudouOUC/qwen-code that referenced this pull request Aug 27, 2026
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed the current review round in e279783.

  • Fixed the Critical drain/reap regression by treating all four cleanup/settlement failure states as condemned during automatic close, while keeping the final agent close bounded.
  • Tightened initialization deadline validation, completed timeout telemetry, preserved abandoned caller-supplied ID backoff metadata, and corrected the settlement-overdue lifecycle documentation.
  • Added coverage for cancellation propagation, malformed trusted deadlines, late cleanup outcomes, admission capacity/fences, all public quarantine reasons, and overdue-to-settled recovery.

Verification:

  • ACP bridge: 816/816 tests passed; package typecheck and Prettier check passed.
  • Core focused tests: 949/949 passed; package typecheck passed.
  • CLI focused coverage: server 1100/1100, dispatch-error 8/8, and trusted-deadline tests 4/4 passed.
  • ESLint passed for every changed TypeScript file.
  • Independent test-engineer probes confirmed both Critical paths stay fail-closed, preserve live sibling use, and reap exactly at the existing bounded close deadline.

The repository-wide build and CLI typecheck still encounter the known pre-existing Ink selection API/type errors in unchanged UI files; no evidence connects those baseline failures to this branch.

Replied to and resolved 16 review threads.

chiga0
chiga0 previously approved these changes Aug 27, 2026

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Deep-tier review of the session initialization deadline and late-cleanup compatibility layer. No blocking findings.

CI bot R1-11 (Critical) — REFUTED: The CI bot claimed channelIsCondemned "still recognizes only isQuarantined/restoreSettlementOverdue". Looking at the PR diff, channelIsCondemned is a new function introduced by this PR at bridge.ts:3482–3489, and it correctly includes all four states: isQuarantined, restoreSettlementOverdue, newSessionCleanupFailed, and newSessionSettlementOverdue. The old two-condition inline check at the former lines 757–758 was replaced by the new channelIsCondemned call. Both call sites that the CI bot flagged (closeIfChildUnheld agentCloseTimeoutMs and entryIsAutoCloseCandidate capability carve-out) already use channelIsCondemned in the submitted code. The "probe" findings the CI bot reported appear to reflect a pre-PR baseline, not the current diff.

Core mechanism — verified correct:

  • freshSessionBlocker() (lines 2646–2662) checks all four condemned states in order, returning the right BridgeChannelUnavailableReason.
  • newSessionSettlementOverdue is cleared at line 4745 when ci.unsettledAbandonedNewSessions.size === 0, i.e., after the last abandoned session settles. Correct.
  • newSessionCleanupFailed is set and never cleared by design — the channel is condemned until drained and reaped. Correct.
  • channelIsCondemned consistently used at all three drain-path call sites (lines 3043, 3151, 3202).

Suggestions (deferred, not blocking):

  • R1-9: Load/restore-path admission counters for abandonedNewSessionSettlements.size lack test coverage.
  • R1-14: Child-side-deadline timeouts don't emit the session.new.public_result telemetry event or the decision= stderr line that the bridge-timer path emits; diagnostic gap.
  • R1-16: Caller-supplied ID fence during abandoned newSession gives a bare RestoreInProgressError without the awaiting_abandoned_cleanup reason or budget-derived retry hint.
  • R1-1/R1-3/R1-12/R1-13: Documentation overclaims quarantine as "temporary" in contexts where newSessionCleanupFailed is permanent until channel death.

CI disclosure: build-cli is SKIPPED on this head — no unit test run evidence for any of the concurrency or cancellation changes. web-shell E2E Smoke passes; Desktop Shell, Test, and daemon E2E checks did not run.

Reviewed with AI assistance.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

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

Scripted assertions: 4824 passed · 0 failed · 4824 total

Flakiness gate: ⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

中文 — 判定:✅ 通过 · 可合入(agent 判定)

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

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

抖动门:⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

Verification report

PR 10268 Deep Verification Report (follow-up round)

Verdict: merge-ready — 4824 scripted assertions executed, 0 failed.
Verified head: e279783462757f58488dc445b5501c5aebedee2a (HEAD^2), base tip a3ec41a2816fa50a80191ca69daa8e63e0ca7355 (HEAD^1).

This is a follow-up round: the previous round verified head 70aaa0d697 and reported
merge-ready. Since then one commit landed (e279783462, "codex: address PR review
feedback") and the base moved to a3ec41a281. The old head is not locally reachable
(depth-2 merge-ref checkout), so the delta since the last round could not be isolated and
every carried-forward measurement below was re-run at the new head, not diffed against
the old report.

中文 — 判定:✅ 通过 · 可合入(agent 判定)

本轮为复验轮:上一轮在 head 70aaa0d697 给出 merge-ready;此后新增提交 e279783462(处理 PR 评审反馈),base 前进到 a3ec41a281。旧 head 在本 depth-2 检出中不可达,无法隔离两轮之间的增量,因此所有沿用的测量均在新 head 上重新执行,而非对照旧报告。

  • 结论merge-ready(断言总数与通过数见文首行及 assertions.json)。
  • A/B 结论(核心主张:初始化超时后对迟到 newSession 的收口):在真实 NDJSON/ACP wire 上、使用两侧编译产物驱动 createAcpSessionBridge。head 38/38:deadline meta 上线、迟到会话被精确 ID 的 sessionClose 恰好关闭一次、清理被拒 → new_session_cleanup_failed 隔离、永不 settle → new_session_settlement_overdue 隔离、settle 后可重新准入、空 channel 走 kill_empty 回收、调用方指定 ID 的 fence 保持到清理完成、协作子进程的 session_initialization_timeout 错误映射回 BridgeTimeoutError、健康兄弟全程 end_turn;base 24/24 bug 形态断言全部命中(隐藏会话泄漏、0 次 close、准入不关闭、fence 提前释放、原始 RPC 错误直通)。见 "Central claim and A/B proof" 表与 01-ab-head-late-newsession-contained.png02-ab-base-hidden-session-leaks.png
  • 上一轮 Finding 1(畸形 deadline 分支无测试):本轮已修复 —— 新增 4 用例 it.each;变异体 M-K 证明其确实被钉住(弱化校验后非整数用例变红)。
  • 变异矩阵:13/13 变异体全部击杀、0 存活(含组合行 M-CD)。见 "Mutation matrix" 表与 03-mutation-matrix-13-of-13-killed.png
  • Hook 进程树:真实进程 A/A 两臂一致(树回收机制来自 base,本 PR 未改 hooks/,只新增信号接线 —— 已由 M-I 钉住):abort 后根进程与忽略 SIGTERM 的后代进程均被回收。见 "Hook process-tree" 表与 04-hook-tree-aa-head-and-base.png
  • 测试门:四个受影响范围全部通过(计数见 "Targeted gates" 表)。
  • Findings:无阻塞项。2 条完整性建议(branch 准入容量的 settle 计数无测试钉住;id_reclaimed/transport_closed 清理分支无单测)。见 "Findings" 节。
  • 未覆盖:真实 CLI child 的完整 daemon E2E、Windows、load/resume 期限传播、prompt 取消、OS 级限制、全仓套件(PR CI 覆盖)、逐提交归因(depth-2)。详见 "Not covered" 节。

Previous-finding status (follow-up round)

# Previous finding (round at 70aaa0d697) Severity Status at e279783462
1 Central claim proven by A/B: late newSession contained, quarantine scoped, siblings usable proof stands, re-measured — new-head A/B: head 38/38 vs base 24/24 bug-shape cells, two green runs per arm (plain + captured)
2 Mutation matrix: 6 mutants, zero survivors proof stands, extended — 13 mutants at the new head, 13/13 killed, 0 survivors (incl. a combination row)
3 Hook tree reclamation A/A 10/10 (machinery is base's #10100) proof stands, re-measured — 6/6 head, 6/6 base, identical; git diff HEAD^1..HEAD -- packages/core/src/hooks still empty
4 Targeted gates green (bridge 1775, core 947+296, cli 1618+1 skip) gate stands, re-run — new counts: bridge 1788, core 955+296, cli 1624+0 skip
5 Finding 1: malformed-deadline invalidParams branches not pinned by any test Suggestion fixed at the new head — 4-case it.each added; mutant M-K proves the pinning (weakening the check turns the non-integer case red)
6 Finding 2: the quarantine reason contract is load-bearing even where admission blocking is redundantly defended Observation stands — M-C is again caught via the expected reason value (new_session_cleanup_failed)
7 Correction: PR description's "bundle blocked by Ink on main" not reproducible Correction still not reproduciblenpm run bundle exits 0 at this merge commit too (bundle-head.log)

Central claim and A/B proof

Central claim: when a managed ACP newSession exceeds the init budget, the caller gets
the existing init_timeout contract AND the underlying child work is contained — a
late-created session is closed by exact ID (never left published-but-unregistered),
quarantine is fail-closed but reason-scoped when cleanup is uncertain, and healthy
sibling sessions remain usable.

Harness: ab-late-newsession.mjs drives the compiled dist of each arm's
createAcpSessionBridge against a scripted fake ACP peer over real NDJSON streams
(AgentSideConnection from @agentclientprotocol/sdk, the package's own wire design via
the compiled internal/testUtils.js fixture — byte-identical across arms, verified).
No code under test is mocked. Base-side cells assert the presence of the bug as scripted
expectations. Each arm ran twice, both green (one plain run + the captured run); the
first base run additionally served to debug four wrong harness predictions of my own
(listed below). Witnesses: 01-ab-head-late-newsession-contained.png,
02-ab-base-hidden-session-leaks.png.

Cell base (a3ec41a281) head (e279783462)
Deadline meta on the wire (S1) absent (child cannot cooperate) present, absolute epoch ~budget away
Public result of the slow spawn BridgeTimeoutError at budget (contract preserved) identical
sessionClose requests for the late session (S1) 0 — late session stays OPEN on the child (hidden session) exactly 1, exact ID late-hidden; child no longer holds it
Spawn while settlement pending, maxSessions=2 (S1) succeeds (slot leak; capacity spent one step later) SessionLimitExceededError (settlement holds the slot)
Close refused → fresh admission (S2) allowed (unknown child state) quarantined new_session_cleanup_failed, retryAfterSeconds > 0
Request never settles → admission (S3) allowed quarantined new_session_settlement_overdue
Overdue, then the request settles (S5) admission never closed admission reopens after settlement
Empty channel timeout (S6) channel killed via the pre-existing isDying cleanup decision=kill_empty; late settle takes transport_closed; no quarantine, late id never registered, next spawn served fresh
Cooperative child enforcing the deadline (S4) raw RPC error {code:-32603} passes through mapped to BridgeTimeoutError fast (<500ms; no full budget burned)
Caller-supplied id fence (S7) released at the public timeout — a second wire registration for the same id goes out (double-register hazard) fenced awaiting_abandoned_cleanup, retryAfterSeconds: 5, for spawn and load; released only after the exact-ID close
Sibling prompt during/after all of the above end_turn everywhere end_turn everywhere
Shutdown with a hung settlement (S3) completes completes < 5s
initializeTimeoutMs validation (S8) rejects via resolveSessionRestoreTimeoutMs's combined message rejects via dedicated checks ("positive integer." / "timer range")

Head: 38/38 scripted assertions ×2 runs. Base: 24/24 ×2 runs.

Discarded base-arm predictions (reported, not buried) — my first base run had four
predictions wrong; each was a harness-prediction bug, investigated before re-running:

  1. base kills an empty channel on newSession timeout too (via doSpawn's isDying cleanup),
    so S6's kill is A/A and the head-only deltas are the settlement tracking;
  2. base also rejects fractional/out-of-range initializeTimeoutMs — from
    resolveSessionRestoreTimeoutMs's shared validator (unmodified by the PR); the PR's
    dedicated checks improve the message/ordering, not throw-vs-not;
  3. base's post-timeout S1 spawn fails the cap because the leaked spawn spent the slot;
  4. S4's raw error is a plain object (code=-32603), not an Error instance.

Corrections

  • Correction to the PR description, re-verified at this head ("the repository-wide
    npm run build && npm run bundle remains blocked before bundling by the current main
    branch's unrelated Ink selection API/type mismatch"): not reproducible at the new merge
    commit either.
    npm run bundle exits 0 here (bundle-head.log: "✅ All bundle assets
    copied to dist/"). The PR touches no CLI UI / Ink files. A statement about the
    description, not a request to change code.

Findings

None blocking.

  1. (Suggestion, completeness) The branch-admission cap check (the restoreBranch
    block inside the branch path, bridge.ts ~L10063) counts
    abandonedNewSessionSettlements.size, but nothing pins it: my first M-F2 mutant removed
    that count and the load/resume capacity tests stayed green because they exercise
    restoreSession's cap (~L7352), not the branch path. The guard is present and correct;
    the test is missing. (Re-targeted M-F2 at the load/resume site was killed, so the cap
    guard itself is pinned on the paths the tests cover.)
  2. (Suggestion, completeness) Two cleanup-result branches of settleAbandonedNewSession
    have no unit test: id_reclaimed (late id already owned by a live session) and
    transport_closed (channel died before cleanup) — grep of bridge.test.ts finds
    neither. transport_closed is exercised behaviorally by the A/B harness S6 (late settle
    after the empty-channel kill: no quarantine, no registration, next spawn fresh);
    id_reclaimed is not exercised anywhere.
  3. (Observation) Per-commit attribution remains out of reach: the checkout is depth 2
    and 70aaa0d697 is not locally reachable, although the metadata snapshot lists two
    commits. The aggregate HEAD^1..HEAD diff is what was verified.

Mutation matrix (vacuity) — 13/13 killed, zero survivors

All mutants applied by exact string replacement at the new head, run through the package's
vitest (-t pinned to the expected catcher, same file as the mutant), restored
byte-for-byte afterward (git status clean at the end). Every mutant ran behind a green
unmutated control in the same file+filter (11 unique controls). Witness:
03-mutation-matrix-13-of-13-killed.png.

Mutant Suite under test Result Red test (behavioral mismatch)
M-A deadline meta removed from wire request bridge.test.ts killed "closes a session created after the public newSession deadline"
M-B errorKind→BridgeTimeoutError mapping removed bridge.test.ts killed "keeps the public timeout contract when the agent enforces the deadline"
M-C newSessionCleanupFailed setter removed bridge.test.ts killed "refuses fresh sessions when late newSession close is refused" (expected reason value)
M-D newSessionSettlementOverdue setter removed bridge.test.ts killed "refuses fresh sessions when an abandoned newSession does not settle"
M-E quarantine-blocker readers removed (both reasons) bridge.test.ts killed both "refuses fresh sessions…" tests (2 red)
M-CD COMBINATION: C+D+E reverted together bridge.test.ts killed both "refuses…" tests — no masking between setters and readers
M-F1 spawn-path cap stops counting settlements bridge.test.ts killed cap cell of "closes a session created after the public newSession deadline"
M-F2 load/resume cap stops counting settlements bridge.test.ts killed "counts an abandoned newSession against load/resume admission capacity" (2 red)
M-G requested-id fence not held after abandonment bridge.test.ts killed "holds a requested id until an abandoned newSession settles"
M-I hook-failure catch stops re-throwing cancellation core client.test.ts killed "passes cancellation to SessionStart hooks and does not swallow it"
M-J trusted-parent gate removed from deadline parsing cli acpAgent.test.ts killed "ignores a forged session initialization deadline from an untrusted parent"
M-K malformed-deadline validation weakened to raw <= 0 cli acpAgent.test.ts killed 1 of the 4 it.each subcases red — the non-integer case, exactly the clause removed; proves previous Finding 1 is now pinned
M-L pre-abort check removed from Config.initialize entry core config.test.ts killed "rejects a pre-aborted initialization without consuming the Config"

Adjudicated probe: the first M-F2 variant targeted the branch-admission cap site
(~L10063) and survived — not because the guard is dead, but because no test walks that
path. Reclassified as Finding 1 above; the re-targeted mutant (load/resume site) was killed.

Hook process-tree reclamation (real processes, A/A)

hook-tree-harness.mjs drives each arm's compiled HookRunner with a real SessionStart
command hook whose root spawns a descendant that traps and ignores SIGTERM (kept in the
hook's process group, like a real tree — a detached descendant would be out of the
group-kill's reach by construction; my first version made that mistake symmetrically on
both arms and the corrected harness is the one reported). Aborts the signal ~0.5s after
the tree is observed alive and asserts on live PIDs. Witness:
04-hook-tree-aa-head-and-base.png.

Check head base
Quick-hook control (harness alive) PASS PASS
Root + descendant alive before abort PASS PASS
Hook returns promptly as cancelled PASS PASS
Escalation within 5s of abort PASS PASS
Root reclaimed PASS PASS
SIGTERM-immune descendant reclaimed (group SIGKILL) PASS PASS
Total 6/6 6/6

A/A by construction: git diff HEAD^1..HEAD -- packages/core/src/hooks is empty at this
head too — the tree-reclamation machinery is base's; the PR's contribution is the signal
wiring (client → config → fireSessionStartEvent(signal)), which mutant M-I and the
acpAgent deadline test pin.

Targeted gates

Gate Result
packages/acp-bridge full suite (34 files) 1788/1788 passed (package total +13 over the previous round's 1775; bridge.test.ts alone now 816 tests) — witness 05-targeted-gates-all-green.png
packages/core changed modules: client.test.ts + config.test.ts 955/955 passed (+8: the new client/config cancellation tests)
packages/core hook files (hookRunner, hook-runner.process, hookSystem, hookEventHandler) 296/296 passed (unchanged — the PR touches no hook files)
packages/cli changed files: acpAgent.test.ts + dispatch-error.test.ts + server.test.ts 1624/1624 passed (+6 new acpAgent tests; the single-reason quarantine tests were replaced by all-reason loops; the previous round's env-skipped test ran and passed in this container)

Gate liveness: proven in the same round before any green result was cited. The mutation
matrix turned the suites red in the identical files as the mutants (bridge.test.ts,
client.test.ts, config.test.ts, acpAgent.test.ts — 13 kills), and 11 green
unmutated controls prove the runner collects and passes the same tests. For the two serve
files, whose sources no matrix mutant touches, a dedicated liveness probe flipped the
quarantine mapping's httpStatus: 503 to 500 in dispatch.ts: dispatch-error.test.ts
went red on 2 tests including the new "carries every quarantine reason and its backoff
hint" (exit 1; source restored, git status clean — liveness-dispatch.log).

Not covered

  • Full daemon E2E with a real CLI child (the Reviewer Test Plan's literal scenario):
    not run, same substitute chain as the previous round and the same reasoning — bridge
    behavior proven over the real ACP wire against a scripted peer that ignores the deadline
    (the "older child" contract); hook tree reclamation proven with real processes; the
    wiring between them pinned by mutation-verified unit tests. Test-plan step mapping:
    step 1 (timeout + no publication + tree reclaimed + sibling usable) → S1/S2/S3 + hook A/A
    • M-I + the acpAgent "Session not constructed" assertion; step 2 (exactly one close by
      exact ID) → S1/S7; step 3 (closed: false and never-settles → documented 503 reasons,
      sibling usable) → S2/S3. All steps were executable through harnesses; none required the
      missing spawn transport.
  • Windows: hook tree code takes a different branch on win32; this container is Linux.
  • Load/resume deadline propagation, prompt cancellation, endpoint-specific deadlines,
    OS-level cgroup/Job Object containment (declared out of scope by the PR).
  • Repo-wide test suite / lint / typecheck: covered by the PR's own CI, not re-run here
    (targeted gates above are the workspace-level equivalent). Bundle was run (Corrections).
  • Per-commit attribution (depth-2 checkout; old head unreachable).
  • The id_reclaimed branch and the branch-admission cap count (Findings 1–2).

Methodology

Environment: node v22.23.2 in the CI container, working tree = refs/pull/10268/merge
(depth 2); npm ci + npm run build completed at HEAD before this round. Base side:
scratch worktree at HEAD^1 with only core + acp-bridge rebuilt (tsc --build via
the repo's TypeScript, run from the base tree); the root node_modules was reused — valid
because the PR changes no package.json/lockfile — with the one internal link re-pointed:
tmp/base-tree/node_modules/@qwen-code/qwen-code-core symlinked into the base tree.
Resolution was asserted with import.meta.resolve(specifier, parent) under
--experimental-import-meta-resolve (the flagless two-arg form silently ignores the
parent — my first check produced a false alarm that way; the flagged check resolves to
tmp/base-tree/packages/core/dist/index.js). Third-party nested installs that the
worktree lacks (core's nested node_modules, lockfile-identical) were symlinked from the
head tree, plus one tsconfig-relative @lydell/node-pty types link — the only divergence
from a stock build, affecting typing, not emitted code. Marker content check:
SESSION_INITIALIZATION_DEADLINE_META_KEY appears 2× in head's dist/bridge.js, 0× in
base's. Harnesses live in the artifact dir (ab-late-newsession.mjs,
hook-tree-harness.mjs, mutate.mjs) and are rerunnable; raw logs alongside
(head-run2.log, base-run2.log, hook-*.log, matrix-run.log, gates.log,
build-base-*.log, bundle-head.log). Evidence images in evidence/.

Flakiness gate log

rounds=5 files=6 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/acpAgent.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/acpAgent.test.ts
file packages/cli/src/serve/acp-http/dispatch-error.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/dispatch-error.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/core/src/config/config.test.ts: (cd packages/core) npx --no-install vitest run ./src/config/config.test.ts
file packages/core/src/core/client.test.ts: (cd packages/core) npx --no-install vitest run ./src/core/client.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridge.test.ts: PPPPP
  packages/cli/src/acp-integration/acpAgent.test.ts: PPPPP
  packages/cli/src/serve/acp-http/dispatch-error.test.ts: PPPP
  packages/cli/src/serve/server.test.ts: PPPP
  packages/core/src/config/config.test.ts: PPPP
  packages/core/src/core/client.test.ts: PPPP

verdict: timeout
summary: only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

--- 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/acpAgent.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/core/src/config/config.test.ts: P (exit 0)
round 1 · packages/core/src/core/client.test.ts: P (exit 0)
round 2 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/core/src/config/config.test.ts: P (exit 0)
round 2 · packages/core/src/core/client.test.ts: P (exit 0)
round 3 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/core/src/config/config.test.ts: P (exit 0)
round 3 · packages/core/src/core/client.test.ts: P (exit 0)
round 4 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 4 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 4 · packages/core/src/config/config.test.ts: P (exit 0)
round 4 · packages/core/src/core/client.test.ts: P (exit 0)
round 5 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 5 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)

Evidence images

01-ab-head-late-newsession-contained

02-ab-base-hidden-session-leaks

03-mutation-matrix-13-of-13-killed

04-hook-tree-aa-head-and-base

05-targeted-gates-all-green

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

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has neither a verdict nor a deferral on e279783462757f58488dc445b5501c5aebedee2a — no APPROVED, CHANGES_REQUESTED, or COMMENTED review of its own. A DISMISSED one does not count: dismiss_stale_reviews voids the bot's approval on every push, which is exactly when a fresh one is needed. If this re-run was meant to review or approve, it did not, and an approval left by another account is a separate vote that does not count as the bot's own.

⚠️ 机器人在 e279783462757f58488dc445b5501c5aebedee2a既没有裁决也没有 defer —— 没有属于它自己的 APPROVEDCHANGES_REQUESTEDCOMMENTED 评审。DISMISSED 不算:dismiss_stale_reviews 会在每次推送时作废机器人的批准,而那恰恰是需要一次新批准的时刻。如果这次重跑本应评审或批准,那么它没有做到;而其他账号留下的批准是另一张票,不能算作机器人自己的。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 — reached the 5-round cap still reporting (rounds 3–5 each found new gaps); terminated by cap, not by convergence.

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

  • packages/acp-bridge/src/bridge.test.ts:12801 — [probe] absolute-deadline contract asserted only as expect.any(Number)
  • packages/acp-bridge/src/bridge.ts:2668 — [probe] per-reason retry-hint ternary has no observing test
  • packages/acp-bridge/src/bridge.ts:13569 — [review] shutdown await of abandoned new-session settlements is untested
  • packages/cli/src/acp-integration/acpAgent.test.ts:2318 — [review] createAndStoreSession's geminiClient.initialize signal is untested
  • packages/acp-bridge/src/bridge.ts:13569 — [probe] shutdown settlement snapshot misses in-flight spawns abandoned later
  • packages/core/src/core/client.test.ts:1106 — [probe] post-success throwIfAborted() in fireSessionStartHook is untested
  • packages/core/src/core/client.test.ts:1121 — [probe] GeminiClient startChat signal seams have no driving test
  • packages/acp-bridge/src/bridge.test.ts:21406 — [probe] acceptance side of the timer-range boundary is unpinned
  • packages/acp-bridge/src/bridge.ts:8972 — [probe] transient capacity double-count between abandonment and inFlightSpawns.delete
中文说明

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

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

未审查:reverse audit — reached the 5-round cap still reporting (rounds 3–5 each found new gaps); terminated by cap, not by convergence。

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

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

Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread docs/developers/qwen-serve-protocol.md Outdated
Comment thread packages/core/src/config/config.test.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread docs/developers/qwen-serve-protocol.md Outdated
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
doudouOUC added a commit to doudouOUC/qwen-code that referenced this pull request Aug 27, 2026
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed the current Critical review finding in e8c9417.

  • Corrected the awaiting_abandoned_cleanup protocol table: the retry hint now explicitly follows the timed-out operation's budget (restore budget for load/resume; initialization budget for a caller-supplied-id spawn), clamped to 5–120 seconds.
  • Verified both hint sources with the focused Bridge tests (2 passed | 814 skipped), then ran Prettier and git diff --check.
  • Replied to and resolved 1 Critical review thread.

To honor the review's stated convergence posture and the repository's review-churn rule, this push intentionally contains no Suggestion-only changes. R1-1, R1-6, R2-2 through R2-7, and the review's explicitly disclosed deferred audit gaps remain recorded for follow-up rather than widening this PR further.

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

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

  • deadline meta contract asserted only as expect.any(Number) (bridge.test.ts:12801) — already reported (round-2 deferred list, review 5043485951)
  • per-reason retry-hint ternary has no observing test on the 503 path (bridge.ts:2668) — already reported (round-2 deferred list, review 5043485951)
  • createAndStoreSession post-config signal checkpoints untested (acpAgent.test.ts:2318) — already reported (round-2 deferred list, review 5043485951)

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.

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

  • docs/developers/qwen-serve-protocol.md:2133 — [review] R1-1 settlement-overdue paragraph still says "settles cleanly first" while the shared finally clears on either outcome — still stands, deferred (convergence posture)
  • packages/core/src/config/config.test.ts:4251 — [review] R1-6 post-Gemini signal cancellation checkpoints still lack isolated coverage — still stands, deferred (convergence posture)
  • packages/cli/src/acp-integration/acpAgent.test.ts:2363 — [review] R2-2 timer-range invalid-deadline case still sits exactly 1 ms past the boundary (wall-clock race) — still stands, deferred (convergence posture)
  • packages/acp-bridge/src/bridge.test.ts:13048 — [review] R2-3 new-session settlement telemetry variants still unpinned (late_result polarity, cleanup_result closed/quarantined, settlement_overdue) — still stands, deferred (convergence postur…
  • docs/developers/qwen-serve-protocol.md:2142 — [review] R2-4 settlement-overdue clearing on late FAILURE settlement still has no test — still stands, deferred (convergence posture)
  • packages/acp-bridge/src/bridge.test.ts:13162 — [review] R2-5 capacity guards' abandoned-settlement term still has no branchSession witness — still stands, deferred (convergence posture)
  • packages/acp-bridge/src/bridge.test.ts:13152 — [review] R2-6 still no test abandons two overlapping newSessions on one channel (multi-token overdue guard) — still stands, deferred (convergence posture)
  • packages/acp-bridge/src/bridge.test.ts:12944 — [review] R2-7 requested-id spawn fence still only driven via loadSession, never resumeSession — still stands, deferred (convergence posture)

Convergence: round 3 posted 13 inline comment(s), 13 of them reported for the first time; the previous round posted 9 (8 new). Findings keep coming back to the same files: packages/acp-bridge/src/bridge.test.ts (findings in round 2; 6 more now); packages/acp-bridge/src/bridge.ts (findings in round 2; 3 more now); packages/cli/src/acp-integration/acpAgent.test.ts (findings in round 2; 2 more now), and 1 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.)

中文说明

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

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

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

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

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

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

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

Comment thread docs/developers/qwen-serve-protocol.md Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/core/src/core/client.test.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

  • R3-8 registered-ID reclamation coverage — already reported at packages/acp-bridge/src/bridge.ts:4657 (comment 3875448773)
  • R3-7 late newSession rejection cleanup coverage — already reported at packages/acp-bridge/src/bridge.ts:4932 (comment 3875448781)
  • R3-9 resolved SessionStart hook cancellation coverage — already reported at packages/core/src/core/client.test.ts:1126 (comment 3875448857)

Convergence: round 3 posted 9 inline comment(s), 9 of them reported for the first time. Findings keep coming back to the same files: packages/acp-bridge/src/bridge.ts (findings in round 2; 3 more now); packages/cli/src/acp-integration/acpAgent.test.ts (findings in round 2; 2 more now); docs/developers/qwen-serve-protocol.md (findings in rounds 1, 2; 1 more now), and 1 more file(s). (Evidence: the previous round was recovered from a marker this account did not post, so those rounds may not be this account's own.) 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] [probe] R3-1: Late abandoned-session cleanup can close a concurrently restored session before the bridge registers it. An anonymous newSession times out, then an in-flight loadSession creates ID X in the child but has not returned to bridge registration. If the abandoned newSession returns X first, byId does not yet contain X, so cleanup sends sessionClose(X); the restore then registers an already-closed session. PR probe: closeCalls = ["collision-id"]; the restore subsequently resolved and bridge.sessionCount became 2. Adding an inFlightRestores.has(lateSessionId) discriminator prevented the close and flipped the probe. Coordinate abandoned cleanup with the same per-ID ownership used by in-flight restore/spawn registration; do not close an ID whose ownership is live or transferring. Add a deferred restore/newSession collision test that resolves the abandoned call with the restore ID before registration and proves no close is sent and the restored session remains usable.

[Critical] [probe] R3-3: The empty-channel timeout path waits for channel termination before returning the public initialization timeout. When the first newSession exceeds initializeTimeoutMs and channel.kill() never settles, the timeout catch awaits kill and its finally path also awaits teardown through reaping, so spawnOrAttach remains pending instead of promptly returning BridgeTimeoutError. With kill returning a never-settling promise, the unmodified PR remained pending; making both timeout teardown calls fire-and-forget made the focused test pass (1 passed, 816 skipped). Mark the channel dying synchronously, retain background reap bookkeeping, and return the timeout without awaiting teardown. Add an empty-channel test with a non-settling kill promise that proves spawnOrAttach rejects after the timer while teardown remains pending.

[Critical] [probe] R3-4: Writer cleanup failure replaces the initialization deadline error with session_writer_unavailable. If the deadline aborts after writer activation and closeSessionWriter rejects during failed-initialization cleanup, initializeOnce wraps both errors in SessionWriterUnavailableError, so the Agent and Bridge no longer expose the session_initialization_timeout/init_timeout contract. The unmodified PR returned SessionWriterUnavailableError instead of the initialization deadline error; preserving options.signal.reason made the focused test pass (1 passed, 582 skipped). Preserve the signal reason as the request result and retain or log cleanup failure separately. packages/acp-bridge/src/bridge.ts maps only SESSION_INITIALIZATION_TIMEOUT_ERROR_KIND to BridgeTimeoutError, so cleanup must not replace that error kind. Add a Config test that aborts after writer activation while writer close rejects and proves the exact abort reason survives ACP error mapping.

中文说明

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

收敛情况:第 3 轮发布了 9 条行内评论,其中 9 条是首次提出。发现反复回到同一批文件:packages/acp-bridge/src/bridge.ts(第 2 轮已出过发现,本轮又有 3 条);packages/cli/src/acp-integration/acpAgent.test.ts(第 2 轮已出过发现,本轮又有 2 条);docs/developers/qwen-serve-protocol.md(第 1、2 轮已出过发现,本轮又有 1 条),另有 1 个文件。(证据说明:上一轮的数据来自并非本账号发布的标记,上述轮次可能不属于本账号。)一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

[Critical] [probe] R3-1: Late abandoned-session cleanup can close a concurrently restored session before the bridge registers it. An anonymous newSession times out, then an in-flight loadSession creates ID X in the child but has not returned to bridge registration. If the abandoned newSession returns X first, byId does not yet contain X, so cleanup sends sessionClose(X); the restore then registers an already-closed session. PR probe: closeCalls = ["collision-id"]; the restore subsequently resolved and bridge.sessionCount became 2. Adding an inFlightRestores.has(lateSessionId) discriminator prevented the close and flipped the probe. Coordinate abandoned cleanup with the same per-ID ownership used by in-flight restore/spawn registration; do not close an ID whose ownership is live or transferring. Add a deferred restore/newSession collision test that resolves the abandoned call with the restore ID before registration and proves no close is sent and the restored session remains usable.

[Critical] [probe] R3-3: The empty-channel timeout path waits for channel termination before returning the public initialization timeout. When the first newSession exceeds initializeTimeoutMs and channel.kill() never settles, the timeout catch awaits kill and its finally path also awaits teardown through reaping, so spawnOrAttach remains pending instead of promptly returning BridgeTimeoutError. With kill returning a never-settling promise, the unmodified PR remained pending; making both timeout teardown calls fire-and-forget made the focused test pass (1 passed, 816 skipped). Mark the channel dying synchronously, retain background reap bookkeeping, and return the timeout without awaiting teardown. Add an empty-channel test with a non-settling kill promise that proves spawnOrAttach rejects after the timer while teardown remains pending.

[Critical] [probe] R3-4: Writer cleanup failure replaces the initialization deadline error with session_writer_unavailable. If the deadline aborts after writer activation and closeSessionWriter rejects during failed-initialization cleanup, initializeOnce wraps both errors in SessionWriterUnavailableError, so the Agent and Bridge no longer expose the session_initialization_timeout/init_timeout contract. The unmodified PR returned SessionWriterUnavailableError instead of the initialization deadline error; preserving options.signal.reason made the focused test pass (1 passed, 582 skipped). Preserve the signal reason as the request result and retain or log cleanup failure separately. packages/acp-bridge/src/bridge.ts maps only SESSION_INITIALIZATION_TIMEOUT_ERROR_KIND to BridgeTimeoutError, so cleanup must not replace that error kind. Add a Config test that aborts after writer activation while writer close rejects and proves the exact abort reason survives ACP error mapping.

— gpt-5.6-sol@954e5164 via Qwen Code /review (v0.22.2)

Comment thread docs/developers/daemon/18-error-taxonomy.md Outdated
Comment thread docs/developers/qwen-serve-protocol.md Outdated
Comment thread packages/acp-bridge/src/bridge.ts Outdated
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/acp-bridge/src/bridge.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
Comment thread packages/cli/src/acp-integration/acpAgent.test.ts
Comment thread packages/core/src/config/config.test.ts
Comment thread packages/core/src/core/client.ts
doudouOUC added a commit to doudouOUC/qwen-code that referenced this pull request Aug 28, 2026
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Critical review fixes pushed in 7a8b1fe03d.

Review action Decision Result
Protocol and error-taxonomy fence/recovery wording Agree Distinguished restore HTTP 504 from session init_timeout; documented late failure, conclusive late-success cleanup, and cleanup-failed transitions.
Channel-wide settlement-overdue flag Agree Replaced it with per-token tracking and added the staggered A/B regression.
Late anonymous ID colliding with an in-flight restore or caller-supplied spawn Agree Added exact-ID ownership reservations so late cleanup cannot close a session another request is creating; covered both collision paths.
Empty-channel timeout blocked by non-settling kill Agree Teardown is marked synchronously and dispatched in the background so the public deadline remains observable.
Config cleanup masking the original abort/deadline Agree Preserved the exact abort reason while retaining AggregateError for ordinary initialization plus cleanup failures.
Remaining coverage and test-hardening Suggestions Deferred Acknowledged in each thread; this PR is beyond the repository review-round churn limit, so non-Critical expansion is deferred.

Verification: ACP bridge full file 820/820; Config full file 583/583; focused exact-head regressions 8/8 and 2/2; both affected package builds and typechecks passed; targeted ESLint and git diff --check passed. The root typecheck remains blocked only by the known unchanged CLI Ink selection API/type errors and missing built Web UI SDK, with no CI evidence connecting either to this branch.

chiga0
chiga0 previously approved these changes Aug 28, 2026

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier: Deep(并发 session 初始化 + overdue 状态机)— 增量 e27978347a8b1fe0

已核验内容

R3-3(channel-wide overdue flag 在唯一超时请求 settle 后不清除)

核心修复:将 newSessionSettlementOverdue: boolean 改为 overdueAbandonedNewSessions: Set<symbol>,按 token 粒度跟踪:

  • 超时时:ci.overdueAbandonedNewSessions.add(token) 替代设置全局 boolean
  • settle 时:ci.overdueAbandonedNewSessions.delete(token) 逐个清除
  • 判断时:ci.overdueAbandonedNewSessions.size > 0 替代 ci.newSessionSettlementOverdue

旧逻辑:flag 只在 unsettledAbandonedNewSessions.size === 0 时才清除(需要全部 settle),导致 A/B 两个超时请求中 A 先 settle 后 flag 仍保持。新逻辑:A settle → A 的 token 被 delete,flag 随即变 false(若 B 未 settle 则 B 的 token 仍在 set 中)。

测试 tracks settlement overdue state per abandoned newSession 验证:两个超时请求中,lateA.reject 后 channel 立即恢复可用(lateB 仍 pending 时通道仍 condemned,lateB.reject 后恢复)。✓

R3-10/R3-11 测试新增

  • returns an empty-channel timeout without waiting for channel kill:验证 kill() 永远 pending 时 timeout 仍正常返回 BridgeTimeoutError 而不阻塞 ✓
  • does not close a late newSession id owned by an in-flight restore / does not close a late newSession id owned by an in-flight spawn:验证 session id 归属判断正确 ✓

R3-12(config.ts:writer cleanup 失败覆盖了 init deadline error)

closeSessionWriter catch 中增加前置检查:若 signal.aborted 且 abort reason 包含 original error,则 log close 失败后 throwIfAborted()——确保初始化超时错误正确传播,不被 close error 覆盖。✓

CI

1 success,1 in_progress(review-pr),28 skipped。核心测试无失败。

无阻塞项。

doudouOUC and others added 4 commits August 28, 2026 11:40
Propagate the daemon initialization deadline through ACP session startup and SessionStart hooks, while containing late results from older agents without disrupting shared-channel siblings.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC
doudouOUC force-pushed the fix/session-initialization-deadline branch from 7a8b1fe to 9bfba01 Compare August 28, 2026 03:41
@github-actions

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Rebased onto current origin/main (bc6f1a015c) and updated the PR head to 9bfba01068.

The three content conflicts were mechanical adaptations to the merged Gemini→Llm rename: startChat retains its AbortSignal and now returns LlmChat; Config preserves the current skipLlmInitialization ?? skipGeminiInitialization compatibility fence while forwarding the signal to llmClient.initialize; ACP session creation forwards the same signal through llmClient.initialize. No product or scope semantics changed.

Verification on the rebased head: focused Core 8/8 and CLI ACP 9/9 passed; the earlier full exact-content runs passed Core 957/957; Core and ACP Bridge typechecks passed; relevant ESLint and git diff --check passed. CLI package typecheck remains blocked only by the known unchanged OpenTUI/Ink selection API mismatches under packages/cli/src/ui/**; no error references the conflict-adapted paths.

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

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

  • session.new.late_result / session.new.settlement_overdue telemetry unpinned — already reported (round-2 R2-3, comment 3873973810)
  • deadline meta contract asserted only as expect.any(Number) — already reported (round-2 deferred list, review 5043485951)
  • per-reason retry-hint ternary has no observing test — already reported (round-2 deferred list, review 5043485951)
  • createAndStoreSession pre-publication signal checkpoints untested — already reported (round-2 deferred list, review 5043485951; round-3 comment 3875658117)
  • id-fence tests negative-only (id_reclaimed settlement unpinned) — already reported (round-3 R3-1, comment 3875448773)

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.

Convergence: round 4 posted 3 inline comment(s), 3 of them reported for the first time; the previous round posted 13 (13 new). Findings keep coming back to the same files: packages/acp-bridge/src/bridge.test.ts (findings in round 3; 2 more now); docs/developers/qwen-serve-protocol.md (findings in round 2; 1 more now). (Evidence: the previous round was recovered from a marker this account did not post and merged over this account's own entries, so some of those rounds and its counts may not be this account's own.) 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.)

中文说明

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

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

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

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

收敛情况:第 4 轮发布了 3 条行内评论,其中 3 条是首次提出;上一轮发布了 13 条(其中 13 条首次提出)。发现反复回到同一批文件:packages/acp-bridge/src/bridge.test.ts(第 3 轮已出过发现,本轮又有 2 条);docs/developers/qwen-serve-protocol.md(第 2 轮已出过发现,本轮又有 1 条)。(证据说明:上一轮的数据来自并非本账号发布的标记,并与本账号自己的条目合并,上述轮次与其计数中的部分可能不属于本账号。)一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread docs/developers/qwen-serve-protocol.md
Comment thread packages/acp-bridge/src/bridge.test.ts
Comment thread packages/acp-bridge/src/bridge.test.ts
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review follow-up for 23a2637

Item Decision Action
R4-1 public init-timeout mapping Fixed ACP and REST now return retryable init_timeout with 504 semantics and a budget-derived retry hint; mapper regression tests added.
R4-2 per-token overdue mutation coverage Deferred Valid test-strengthening suggestion, but no current correctness defect; deferred under the review-round churn rule.
R4-3 cleanup-failure recycle mutation coverage Deferred Valid test-strengthening suggestion, but current ChannelInfo-scoped recovery is correct; deferred under the review-round churn rule.

Verification: 44 focused mapper tests passed; the existing REST restore-timeout route test passed and preserved restore_timeout; focused Prettier, ESLint, and git diff checks passed. CLI typecheck remains blocked only by the pre-existing unchanged Ink selection API/type failures.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

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

  • R4-2 staggered overdue test cannot kill the clear-all mutant (bridge.test.ts:13336) — still stands; already reported (round-4 comment 3878640381), author deferred
  • R4-3 cleanup-failed recovery lacks channel-recycle mutation coverage (bridge.test.ts:13184) — still stands; already reported (round-4 comment 3878640386), author deferred

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 5": traced neither the session-creation route's catch block (which responder it forwards bridge errors to) nor the RPC newSession dispatch path — this is why Find…; "agent 1a": did not execute the two new test files — the worktree has no built package dist/ outputs (vitest's globalSetup guard would halt the run) and building in the s….

中文说明

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

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

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

未探索到全部深度(达到工具调用预算):"agent 5"traced neither the session-creation route's catch block (which responder it forwards bridge errors to) nor the RPC newSession dispatch path — this is why Find…"agent 1a"did not execute the two new test files — the worktree has no built package dist/ outputs (vitest's globalSetup guard would halt the run) and building in the s…

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

Comment thread packages/cli/src/serve/server/error-response.ts Outdated

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

Independent pass at 23a2637, focused on the cancel-vs-complete race (prior 51 threads all resolved; bot CHANGES_REQUESTED treated as stale). No substantiated findings. The design avoids the naive cancel race properly: a timed-out newSession is not force-cancelled mid-flight; it is recorded (recordNewSessionPublicTimeout), the channel decision is kill_empty when there is no other work or fence_shared otherwise, and the abandoned spawn is tracked via inFlightSessionIdReservations settlement promises with a settlement grace timer (newSessionSettlementGraceMs = initTimeoutMs) that marks the channel overdue and refuses fresh sessions until the abandoned init settles or drains — mirroring the existing restore-settlement machinery, so a late-completing init cannot orphan a session id or collide with a retry. Timers are unref-ed and cleared on channel death; initializeTimeoutMs is validated as a positive integer within the setTimeout range. Residual (not a finding): I did not trace reapPendingEmptyChannel end-to-end against concurrent spawn admission under fence release; the added bridge tests (672 lines) target that area.

yiliang114
yiliang114 previously approved these changes Aug 28, 2026

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

Approving. Re-verified at head 23a2637: the channel-wide overdue boolean is gone (0 occurrences of newSessionSettlementOverdue) and replaced by the per-token overdueAbandonedNewSessions Set (bridge.ts:947, add at :3541, per-token delete in the settle finally at :4779, admission reads size>0 at :2657/:3487) — exactly the staggered-two-token defect class reported. Both doc findings are fixed too (recovery-point semantics now distinguish late-failure vs late-success cleanup at protocol.md:2175/2184 and error-taxonomy.md:62; the fence-table row no longer claims a blanket 504). All 51 threads resolved; the test-pin asks from the last round are in (id_reclaimed, abandoned-phase reject, drainTimeoutMs spot-checked). Known leftover: the 'beyond the timer range' real-clock flake (acpAgent.test.ts:2362) remains, previously accepted as non-blocking. CI fully green.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

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

Scripted assertions: 4831 passed · 0 failed · 4831 total

Flakiness gate: ⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

中文 — 判定:✅ 通过 · 可合入(agent 判定)

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

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

抖动门:⚠️ timeout — only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

Verification report

PR 10268 Deep Verification Report (follow-up round 2)

Verdict: merge-ready — 4831 scripted assertions executed, 0 failed.
Verified head: 23a2637d261981b9365f24af18313f07d81f0807 (HEAD^2), base tip 4b5396c69a35adc4041f665fd413aaec4712e9c3 (HEAD^1).

This is a follow-up round: the previous round verified head e279783462 and reported
merge-ready. Since then the head moved to 23a2637d26 (metadata snapshot lists five commits,
all codex: address PR review feedback fixups after the original) and the base moved to
4b5396c69a. The old head is not locally reachable (depth-2 merge-ref checkout:
git cat-file -t e279783462… fails; git rev-list HEAD^1..HEAD^2 returns 1 commit against the
snapshot's 5 — the shallow boundary). The delta therefore could not be isolated by diffing; it
was reconstructed from the previous report's own measurements — bridge.test.ts grew
816 → 820 tests, i.e. four tests landed since the last round — and every carried-forward
measurement below was re-run at the new head, never diffed against the old report.

中文 — 判定:✅ 通过 · 可合入(agent 判定)

本轮为复验轮(第二次):上一轮在 head e279783462 给出 merge-ready;此后 head 前进到
23a2637d26(快照列出五个 codex: address PR review feedback 提交),base 前进到 4b5396c69a
旧 head 在本 depth-2 检出中不可达,无法直接隔离增量;通过上一轮报告自身的测量值重建增量
(bridge.test.ts 从 816 增至 820 个测试,即新增 4 个测试),所有沿用的测量均在新 head 上重新执行

  • 结论merge-ready(断言总数与通过数见文首行及 assertions.json)。
  • A/B 结论(核心主张:初始化超时后对迟到 newSession 的收口):在真实 NDJSON/ACP wire 上驱动两侧编译产物。head 59/59、base 37/37 bug 形态断言全部命中(隐藏会话泄漏、0 次 close、准入不关闭、fence 提前释放、原始 RPC 错误直通、空 channel 回收)。新增 S9 场景以真实定时器验证 in-flight spawn 所有权等待,并顺带以 telemetry 证据覆盖了上一轮 Finding 2 中 id_reclaimed 分支。见 "Central claim and A/B proof" 表与 01-ab-head-late-newsession-contained.png02-ab-base-hidden-session-leaks.png
  • 变异矩阵:13/13 变异体全部击杀;M-BranchCap(branch 准入容量计数)再次对全量 820 个测试存活 → 上一轮 Finding 1 仍然成立(覆盖缺口)。针对新增 4 个测试的 3 个增量变异体:M-H1/M-H3 击杀;M-H2 首次运行"存活"系本验证自身变异体构造缺陷(删除变量声明导致悬空引用、settle 静默崩溃),修正为外科手术式变异体后被仓库自带测试击杀(1 红)且 mutant dist 构建上 S9 三格变红 —— spawn-owner 等待确认被钉住。见 "Mutation matrix" 表与 04-mutation-matrix-13-killed-1-adjudicated.png
  • Hook 进程树:真实进程 A/A 两臂一致 6/6(时序几乎相同:取消返回 2001/2002ms,回收 2013/2009ms;树回收机制来自 base,本 PR 未改 hooks/ —— git diff HEAD^1..HEAD -- packages/core/src/hooks 为空)。见 03-hook-tree-aa-head-and-base.png
  • 测试门:四个受影响范围全部通过(计数见 "Targeted gates" 表);活性探针(dispatch.ts 503→500)使 2 个测试变红,门禁为活。
  • Findings:无阻塞项。2 条完整性建议均为上一轮沿用(branch 准入容量无测试钉住;id_reclaimed/transport_closed 仍无单测,但本轮 harness 已以真实行为覆盖两者)。
  • 未覆盖:真实 CLI child 的完整 daemon E2E、Windows、load/resume 期限传播、prompt 取消、OS 级限制、全仓套件(PR CI 覆盖)、逐提交归因(depth-2)。详见 "Not covered" 节。

Previous-finding status (follow-up round)

# Previous finding (round at e279783462) Severity Status at 23a2637d26
1 Central claim proven by A/B: late newSession contained, quarantine scoped, siblings usable proof stands, re-measured — harness extended with S9 (ownership wait + id_reclaimed); head 59/59 vs base 37/37, plus captured runs of both arms
2 Mutation matrix: 13 mutants, zero survivors proof stands, re-run — 13/13 killed again at the new head (incl. combination row M-CD); 3 delta mutants added for the four new tests
3 Hook tree reclamation A/A 6/6 (machinery is base's #10100) proof stands, re-measured — 6/6 head, 6/6 base, near-identical timings (cancel-return 2001/2002ms, reclaim 2013/2009ms); git diff HEAD^1..HEAD -- packages/core/src/hooks still empty
4 Targeted gates green (bridge 1788 pkg, core 955+296, cli 1624) gate stands, re-run — new counts: acp-bridge package 1810 (bridge.test.ts 820), core changed 957, core hooks 296, cli changed 1659
5 Finding 1: branch-admission cap settle count not pinned by any test Suggestion stands — M-BranchCap (removing abandonedNewSessionSettlements.size from the branch-path cap) again survives against the FULL bridge suite (820 tests green); the guard at bridge.ts L10113 is present and correct, the test is still missing
6 Finding 2: id_reclaimed / transport_closed cleanup branches have no unit test Suggestion stands as stated, partially mitigated — grep of bridge.test.ts still finds neither string; this round's harness now exercises both behaviorally against the compiled bridge with telemetry witnesses (S6 → transport_closed, S9 → id_reclaimed), but that is harness coverage, not a committed test
7 Correction: PR description's "bundle blocked by Ink on main" not reproducible Correction still not reproduciblenpm run bundle exits 0 at this merge commit too ("✅ All bundle assets copied to dist/")

Central claim and A/B proof

Central claim: when a managed ACP newSession exceeds the init budget, the caller gets the
existing init_timeout contract AND the underlying child work is contained — a late-created
session is closed by exact ID (never left published-but-unregistered), quarantine is fail-closed
but reason-scoped when cleanup is uncertain, and healthy sibling sessions remain usable.

Harness: ab-late-newsession.mjs drives the compiled dist of each arm's
createAcpSessionBridge against a scripted fake ACP peer over real NDJSON streams
(AgentSideConnection from @agentclientprotocol/sdk, the package's own wire design via the
compiled internal/testUtils.js fixture). No code under test is mocked. Base-arm cells assert
the presence of the bug as scripted expectations, so an all-green base run is the predicted
failure of the old behavior. Head ran green 4× (plain and captured, before and after the S9
extension); base green 3× (after the two harness-prediction fixes documented below).
Witnesses: 01-ab-head-late-newsession-contained.png,
02-ab-base-hidden-session-leaks.png.

Cell base (4b5396c69a) head (23a2637d26)
Deadline meta on the wire (S1) absent (child cannot cooperate) present, absolute epoch ~budget away
Public result of the slow spawn (S1) BridgeTimeoutError at budget (contract preserved) identical
sessionClose requests for the late session (S1) 0 — child created 3 sessions, the hidden one is never closed exactly 1, exact ID late-hidden
Spawn while settlement pending, maxSessions=2 (S1) succeeds (no settlement accounting) SessionLimitExceededError (settlement holds the slot)
Telemetry of the timeout decision (S1/S6) none — the timeout is unobservable session.new.public_result with channel_was_empty
Close refused → fresh admission (S2) allowed (unknown child state) quarantined new_session_cleanup_failed, retryAfterSeconds=5; sibling end_turn; shared channel not killed
Request never settles → admission (S3) allowed quarantined new_session_settlement_overdue; shutdown with hung settlement completes <5s
Overdue, then the request settles (S5) admission never closed admission reopens after settlement
Cooperative child enforcing the deadline (S4) raw RPC error passes through (plain object, code=-32603, errorKind unmapped) mapped to BridgeTimeoutError fast (452ms at a 500ms budget — no second budget burned)
Caller-supplied id fence (S7) released at the public timeout — a second wire registration for the same id is dispatched fenced awaiting_abandoned_cleanup, retryAfterSeconds=5, for spawn and load; released only after the exact-ID close; retry then succeeds
Empty channel timeout (S6) channel killed via pre-existing isDying cleanup (A/A) decision=kill_empty; late settle takes transport_closed (deterministic via delayed kill); no quarantine, late id never registered, next spawn served by a fresh channel
In-flight spawn owns the late id (S9) raw late response dropped, no close machinery no close ever sent; settle records cleanup_result: id_reclaimed once the owner registers
Sibling prompt during/after all of the above end_turn everywhere end_turn everywhere
initializeTimeoutMs validation (S8) rejects via resolveSessionRestoreTimeoutMs's combined message for non-integers dedicated checks: "positive integer." (0, −5, 0.5, NaN) / "timer range" (>2³¹−1)

Head: 59/59 scripted assertions. Base: 37/37.

Discarded base-arm predictions (reported, not buried) — two harness-prediction bugs of my
own, corrected after a base debug run: (1) base's post-timeout S1 spawn succeeds rather than
failing the cap — the leaked spawn's slot is released with the rejected wrapper and the late
response is dropped, so on base nothing ever spends the hidden session's capacity (the leak is
on the child, invisible to the bridge); (2) S8's base message for 0/−5 is
"Must be > 0." from the bridge's own check, while 0.5/NaN/>2³¹ fall through to the shared
validator's "no greater than 2147483647" message (predicted exactly from base source after the
first run).

Delta focus: the four tests added since the previous round

bridge.test.ts grew 816 → 820 (+4). The tests covering the settlement-ownership and
admission-reopen machinery are:
does not close a late newSession id owned by an in-flight restore, …owned by an in-flight spawn, reopens fresh admission when an overdue newSession settles cleanly,
tracks settlement overdue state per abandoned newSession. Targeted mutants:

Mutant Target Result
M-H1 remove the restore-owner wait in settleAbandonedNewSession killed — "…owned by an in-flight restore" red
M-H2 remove the spawn-owner wait (surgical, declaration kept) killed — see adjudication below
M-H3 remove overdueAbandonedNewSessions.delete(token) from settle's finally killed — "reopens fresh admission…" red

M-H2 adjudication (harness error found and corrected): the first-draft M-H2 deleted the
spawnOwner declaration along with the wait, leaving a dangling identifier that the
--noCheck emit carried into both the vitest run and any dist build — settle crashed with a
ReferenceError, dispatched no close, and the test passed for the wrong reason (a mutant
that breaks the mechanism differently is not a probe of the mechanism). The surgical mutant
(declaration kept, wait-and-continue removed) was killed twice: by the repository's own vitest
test (1 failed, run in an isolated HEAD worktree so the flakiness gate's in-flight runs were
never exposed to mutated source) and by the real-timer S9 harness against the mutant's rebuilt
dist — the bridge dispatched sessionClose for requested-in-flight while a live spawn was
still registering it
(cleanup_result: closed instead of id_reclaimed; S9 56/59, exactly
the 3 ownership cells red, all other scenarios green — the mutant was surgical). The
spawn-owner wait is load-bearing and pinned.

Mutation matrix (vacuity) — 13/13 killed, one adjudicated survivor, 3/3 delta killed

All mutants applied by exact string replacement at the new head, run through the package's
vitest (-t pinned to the expected catcher, same file as the mutant), restored byte-for-byte
afterward (git status clean at the end; restores went through saved pristine backups after a
reverse-replace ambiguity was caught in a roundtrip sanity check). Every mutant ran behind a
green unmutated control in the same file+filter (11 controls, C1–C11). Witness:
04-mutation-matrix-13-killed-1-adjudicated.png (the M-H2 row visible in the witness is the
first-draft run; its correction is documented in "Delta focus").

Mutant Suite under test Result Red test (behavioral mismatch)
M-A deadline meta removed from wire request bridge.test.ts killed "closes a session created after the public newSession deadline"
M-B errorKind→BridgeTimeoutError mapping removed bridge.test.ts killed "keeps the public timeout contract when the agent enforces the deadline"
M-C newSessionCleanupFailed setter removed bridge.test.ts killed "refuses fresh sessions when late newSession close is refused" (the settle test stayed green — no masking)
M-D newSessionSettlementOverdue setter removed bridge.test.ts killed "refuses fresh sessions when an abandoned newSession does not settle" (the cleanup test stayed green)
M-E quarantine-blocker readers removed (both reasons) bridge.test.ts killed both "refuses fresh sessions…" tests (2 red)
M-CD COMBINATION: C+D+E reverted together bridge.test.ts killed both "refuses…" tests — no masking between setters and readers
M-F1 spawn-path cap stops counting settlements bridge.test.ts killed cap cell of "closes a session created after the public newSession deadline"
M-F2 load/resume cap stops counting settlements bridge.test.ts killed "counts an abandoned newSession against load/resume admission capacity" (2 red)
M-G requested-id fence not marked abandoned bridge.test.ts killed "holds a requested id until an abandoned newSession settles"
M-I hook-failure catch stops re-throwing cancellation core client.test.ts killed "passes cancellation to SessionStart hooks and does not swallow it"
M-J trusted-parent gate removed from deadline parsing cli acpAgent.test.ts killed "ignores a forged session initialization deadline from an untrusted parent"
M-K malformed-deadline validation weakened to raw <= 0 cli acpAgent.test.ts killed exactly 1 of the 4 it.each subcases red — the non-integer case, the clause removed
M-L pre-abort check removed from Config.initialize entry core config.test.ts killed "rejects a pre-aborted initialization without consuming the Config"
M-H1 restore-owner wait removed (delta) bridge.test.ts killed "does not close a late newSession id owned by an in-flight restore"
M-H2 spawn-owner wait removed, surgical (delta) bridge.test.ts killed "…owned by an in-flight spawn" (isolated-tree run) + S9 mutant-build cells
M-H3 overdue-token delete removed (delta) bridge.test.ts killed "reopens fresh admission when an overdue newSession settles cleanly"
M-BranchCap branch-admission cap stops counting settlements bridge.test.ts full file SURVIVED — adjudicated 820/820 green; classified as a coverage gap (Finding 1), not dead code: the guard is read on a path no test walks with settlements pending

Positive control for the survivor: the same vitest invocation shape killed 15 other mutants in
the same file, and the full-file run collected all 820 tests (15.6s), so the green is evidence
of absence, not a collection failure.

Hook process-tree reclamation (real processes, A/A)

hook-tree-harness.mjs drives each arm's compiled HookRunner with a real SessionStart
command hook whose root spawns a descendant that traps and ignores SIGTERM (kept in the hook's
process group, like a real tree). Aborts the signal ~0.5s after the tree is observed alive and
asserts on live PIDs. Witness: 03-hook-tree-aa-head-and-base.png.

Check head base
Quick-hook control (harness alive) PASS PASS
Root + SIGTERM-immune descendant alive before abort PASS PASS
Hook returns promptly as cancelled PASS (2001ms) PASS (2002ms)
Escalation + reclaim within 5s of abort PASS (2013ms) PASS (2009ms)
Root reclaimed PASS PASS
SIGTERM-immune descendant reclaimed (group SIGKILL) PASS PASS
Total 6/6 6/6

A/A by construction: git diff HEAD^1..HEAD -- packages/core/src/hooks is empty at this head
too — the tree-reclamation machinery is base's; the PR's contribution is the signal wiring
(client → config → fireSessionStartEvent(signal)), which mutant M-I and the acpAgent deadline
tests pin.

Targeted gates

Gate Result
packages/acp-bridge full package suite (34 files) 1810/1810 passed (package total +22 over the previous round's 1788; bridge.test.ts alone now 820 tests — was 816)
packages/core changed modules: client.test.ts + config.test.ts 957/957 passed
packages/core hook files (hookRunner, hook-runner.process, hookSystem, hookEventHandler) 296/296 passed (unchanged — the PR touches no hook files)
packages/cli changed files: acpAgent.test.ts + dispatch-error.test.ts + server.test.ts + error-response.test.ts 1659/1659 passed

Gate liveness: the mutation matrix turned suites red in the identical files as the mutants
(15 kills across bridge.test.ts, client.test.ts, config.test.ts, acpAgent.test.ts), and
11 green unmutated controls prove the runner collects those same tests. For the serve files,
whose sources no matrix mutant touches, a dedicated probe flipped the quarantine mapping's
httpStatus: 503 to 500 in dispatch.ts: dispatch-error.test.ts went red on 2 tests
including the new "carries every quarantine reason and its backoff hint" (exit 1; source
restored, git status clean — gate-liveness-dispatch.log).

Corrections

  • Correction to the PR description, re-verified at this head ("the repository-wide
    npm run build && npm run bundle remains blocked before bundling by the current main
    branch's unrelated Ink selection API/type mismatch"): not reproducible at this merge
    commit either
    npm run bundle exits 0 (bundle-head.log: "✅ All bundle assets
    copied to dist/"). Third consecutive round with the same result; the PR touches no CLI UI /
    Ink files. A statement about the description, not a request to change code.

Findings

None blocking.

  1. (Suggestion, completeness — stands from the previous round) The branch-admission cap
    check (bridge.ts L10113, the restoreBranch block) counts
    abandonedNewSessionSettlements.size, but M-BranchCap — removing exactly that term — again
    survives against the full bridge suite (820 tests). The guard is present and correct;
    no test walks the branch path with a settlement pending. A test to write, not a code change.
  2. (Suggestion, completeness — stands, partially mitigated) The id_reclaimed and
    transport_closed cleanup-result branches of settleAbandonedNewSession still have no unit
    test (grep of bridge.test.ts finds neither string). This round's A/B harness now exercises
    both against the compiled bridge with telemetry witnesses — S6 drives transport_closed
    deterministically (delayed-kill trick) and S9 drives id_reclaimed — but harness coverage
    in a verification artifact is not a committed regression test.
  3. (Observation) Per-commit attribution remains out of reach: the checkout is depth 2, the
    snapshot lists 5 commits, git rev-list HEAD^1..HEAD^2 returns 1, and the previous head
    e279783462 is unreachable. The aggregate HEAD^1..HEAD diff is what was verified.

Flakiness gate

5 rounds × 6 changed test files, one file per vitest invocation: 30/30 runs green,
verdict pass
(flakiness.log; per-invocation logs in flakiness/).

per-file results (P=pass F=fail, one letter per run):
  src/bridge.test.ts: PPPPP
  src/core/client.test.ts: PPPPP
  src/config/config.test.ts: PPPPP
  src/serve/server.test.ts: PPPPP
  src/serve/acp-http/dispatch-error.test.ts: PPPPP
  src/acp-integration/acpAgent.test.ts: PPPPP

Unlike the previous round (4 of 5 rounds fit its 15-minute budget), this round completed all
5 rounds; rounds were slower here (~9 min each on this shared loaded runner) but nothing
failed and no round disagreed.

Not covered

  • Full daemon E2E with a real CLI child (the Reviewer Test Plan's literal scenario): not
    run, same substitute chain as the previous rounds and the same reasoning — bridge behavior
    proven over the real ACP wire against a scripted peer that ignores the deadline (the "older
    child" contract); hook tree reclamation proven with real processes; the wiring between them
    pinned by mutation-verified unit tests. Test-plan step mapping: step 1 (timeout + no
    publication + tree reclaimed + sibling usable) → S1/S2/S3 + hook A/A + M-I; step 2 (exactly
    one close by exact ID) → S1/S7; step 3 (closed: false and never-settles → documented 503
    reasons, sibling usable) → S2/S3. All steps were executable through harnesses. This harness
    reproduces the wire shape of the reported bug, not a model-side degradation that would
    produce a slow initialization in the wild.
  • Windows: hook tree code takes a different branch on win32; this container is Linux.
  • Load/resume deadline propagation, prompt cancellation, endpoint-specific deadlines,
    OS-level cgroup/Job Object containment (declared out of scope by the PR).
  • Repo-wide test suite / lint / typecheck: covered by the PR's own CI, not re-run here
    (targeted gates above are the workspace-level equivalent). Bundle was run (Corrections).
    Base-side dist was built with tsc --build --force --noCheck — type-checking is the PR CI's
    gate; the control needed identical emitted code, which noCheck preserves.
  • Per-commit attribution (depth-2 checkout; previous head unreachable).

Methodology

Environment: node v22.23.2 in the CI container, working tree = refs/pull/10268/merge
(depth 2); npm ci + npm run build completed at HEAD before this round. Base side: scratch
worktree at HEAD^1 with only core + acp-bridge rebuilt (tsc --build --force --noCheck,
TypeScript 5.8.3); the root node_modules was reused — valid because the PR changes no
package.json/lockfile (git diff HEAD^1..HEAD --stat over all manifest/lock paths is empty) —
with the internal links re-pointed: tmp/base-tree/node_modules/@qwen-code/{qwen-code-core,acp-bridge}
symlinked into the base tree; resolution asserted with the flagged two-arg
import.meta.resolve(specifier, parent) (base core resolves into the base tree, the ACP SDK
resolves to the identical physical file on both arms). Third-party nested installs were
symlinked from the head tree (lockfile-identical), plus the tsconfig-relative
@lydell/node-pty types link at tmp/node_modules/… (typing only, no emitted-code effect).
An environmental TS7016 around @lydell/node-pty's exports-map types blocks checked from-scratch
builds in a worktree; --noCheck is the documented bypass and does not alter emit. Marker
content check: sessionInitializationDeadlineMs appears 1× in head's dist/bridgeTypes.js and
0× in base's; SESSION_INITIALIZATION_DEADLINE_META_KEY 2×/0× in dist/bridge.js. The M-H2
adjudication ran in a third isolated worktree (tmp/head-mutant, removed afterward) so the
flakiness gate's in-flight vitest runs never saw mutated source or dist. Harnesses live in the
artifact dir (ab-late-newsession.mjs, hook-tree-harness.mjs, mutate.mjs, run-matrix.sh,
run-delta-matrix.sh, run-gates.sh, run-flakiness.sh) and are rerunnable; raw logs
alongside (head-run*.log, base-run*.log, hook-*.log, matrix/*.log, gate-*.log,
flakiness/, build-base-*.log). Evidence images in evidence/.

Flakiness gate log

rounds=5 files=7 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/acpAgent.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/acpAgent.test.ts
file packages/cli/src/serve/acp-http/dispatch-error.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/dispatch-error.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/error-response.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/error-response.test.ts
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/core/client.test.ts: (cd packages/core) npx --no-install vitest run ./src/core/client.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/bridge.test.ts: PPPPP
  packages/cli/src/acp-integration/acpAgent.test.ts: PPPP
  packages/cli/src/serve/acp-http/dispatch-error.test.ts: PPPP
  packages/cli/src/serve/server.test.ts: PPPP
  packages/cli/src/serve/server/error-response.test.ts: PPPP
  packages/core/src/config/config.test.ts: PPPP
  packages/core/src/core/client.test.ts: PPPP

verdict: timeout
summary: only 4 of 5 rounds fit the 15-minute budget; the completed rounds agreed

--- 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/acpAgent.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 1 · packages/core/src/config/config.test.ts: P (exit 0)
round 1 · packages/core/src/core/client.test.ts: P (exit 0)
round 2 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 2 · packages/core/src/config/config.test.ts: P (exit 0)
round 2 · packages/core/src/core/client.test.ts: P (exit 0)
round 3 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 3 · packages/core/src/config/config.test.ts: P (exit 0)
round 3 · packages/core/src/core/client.test.ts: P (exit 0)
round 4 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 4 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/server/error-response.test.ts: P (exit 0)
round 4 · packages/core/src/config/config.test.ts: P (exit 0)
round 4 · packages/core/src/core/client.test.ts: P (exit 0)
round 5 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)

Evidence images

01-ab-head-late-newsession-contained

02-ab-base-hidden-session-leaks

03-hook-tree-aa-head-and-base

04-mutation-matrix-13-killed-1-adjudicated

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

Qwen Code · sandboxed verification

@chiga0 chiga0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Round 3 — new head 23a2637d, independent re-review.

Prior findings: R1 and R2 both dismissed after author updated. This review is fresh against current head.


Scope

Tier: Deep. Changes touch ACP session lifecycle protocol, timer ordering, late-settlement quarantine state machine, and AbortSignal cancellation chain. All 20 changed files were read; cross-file context was fetched for the key contracts.

CI:

Job Result
Test (ubuntu-latest, Node 22.x) ✅ pass (34m26s)
Test (macos-latest, Node 22.x) ⚠️ SKIPPED
Test (windows-latest, Node 22.x) ⚠️ SKIPPED
Integration Tests (CLI, No Sandbox) ⚠️ SKIPPED
Real daemon E2E / Java 11 ✅ pass

Platform-specific timer resolution and path handling are unreviewed dimensions. Author confirms macOS-only manual testing.


What I verified

Contract chain: Bridge sends SESSION_INITIALIZATION_DEADLINE_META_KEY = Date.now() + initTimeoutMs; agent reads it only when isTrustedManagedParent(), validates positive safe integer ≤ 2^31-1, converts to remainingMs = raw - Date.now(), arms AbortController. Forged deadlines from untrusted callers are silently ignored — confirmed by the untrusted-parent test.

Signal propagation: agent.newSession → config.initialize → llmClient.initialize → startChat → fireSessionStartHook → hookSystem.fireSessionStartEvent. Signal passed at each hop. fireSessionStartHook catch block calls signal?.throwIfAborted() before warn+swallow, which surfaces cancellation even when the hook throws independently (tested at client.test.ts:1108).

Session publication order: Abort checkpoint after registerCreateSubSessionTool in createAndStoreSession fires before this.sessions.set, consistent with the comment "BEFORE the session is published." Abort here leaves nothing in the session map; settleAbandonedNewSession(ci, token, undefined, ...) exits early in the finally block cleanly.

Quarantine state machine: channelIsCondemned covers all four states. Used in confirmChildUnheld, closeSessions, and maybeCloseIdleSession — verified by grep.

Session cap accounting: abandonedNewSessionSettlements.size at three cap-check sites. Test at bridge.test.ts:12761 confirms a timed-out spawn counts against maxSessions until abandoned settlement resolves.

initTimeoutMs validation: Tightened to positive integer ≤ 2^31-1. Required for Date.now() + initTimeoutMs arithmetic.

settleAbandonedNewSession loop: spawnOwner && lateSessionId !== requestedSessionId guard correctly waits for an in-flight spawn owning the id, passes through when the late id IS the requested id. Tested by "does not close a late newSession id owned by an in-flight spawn."

_meta now unconditional: Backward-compatible — legacy agents ignore unknown fields.


Cross-check against existing reviews

Confirmed:

bridgeErrors.ts JSDoc ("settlement-overdue states may clear when the abandoned request settles") — For late successes, the state actually clears in settleAbandonedNewSession's finally block only after the sessionClose call completes, not at raw request settlement. "When the abandoned request settles" understates this for the success path. The protocol doc 503-entry is precise ("after a late success completes its exact-ID cleanup"); this JSDoc should match.

docs/developers/qwen-serve-protocol.md POST /session section — mentions "init timeout" in prose at the coalescing note, but has no formal 504 init_timeout error entry, even though error-response.ts now returns 504 / init_timeout / retryable:true / Retry-After for BridgeTimeoutError. The error contract and Retry-After semantics for timed-out session creation are not discoverable from the spec.

Cannot rule:

R2-1 round-3 fix-induced finding — I traced the Retry-After plumbing through error-response.ts and dispatch.ts and do not see an obvious contradiction at head, but the original R2-1 inline comment is not visible to me. Recording as a miss I cannot rule on.


My own findings

None beyond the two confirmed above (documentation precision).

Reviewed with AI assistance.

Comment thread packages/acp-bridge/src/bridgeErrors.ts
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on 23a2637d261981b9365f24af18313f07d81f0807 is a COMMENTED one, which carries no vote — so it has no verdict of its own on this commit, and main needs two approving reviews: an approval left by another account is a separate vote and does not count as the bot's own. Two different things look like this, and the stage-3 comment above says which: the triage skill deferring on purpose at 3/5 — a fork refactor hitting the approval guardrail, or a core change escalated for maintainer awareness, both normal outcomes — or an earlier approval that a push dismissed, leaving only the comment behind, which needs a fresh review.

⚠️ 机器人在 23a2637d261981b9365f24af18313f07d81f0807 上唯一的评审是 COMMENTED不带票 —— 因此它在该 commit 上没有自己的裁决,而 main 需要两个批准(其他账号的批准是另一张票)。有两种情况长这样,上方的 stage-3 评论会说明是哪一种:triage skill 在 3/5 时有意 defer(fork refactor 命中审批护栏,或核心改动被升级交由维护者把关,两者都是正常结果);或者更早的批准被一次推送作废、只剩下这条评论,此时需要重新评审。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Round 3 documentation follow-up for 23a2637

Item Decision Action
BridgeChannelUnavailableReason lifecycle JSDoc Agree Exact-head behavior is more precise than the comment: late failure clears on raw settlement, while late success clears only after exact-ID cleanup. Deferred as documentation-only precision under the five-plus-round churn rule.
Formal POST /session 504 init_timeout contract in the protocol guide Agree The route currently returns 504, Retry-After, init_timeout, and retryable: true, but the endpoint section does not formally enumerate it. Deferred as documentation-only discoverability under the same churn rule.

No correctness, security, data-loss, or regression defect was identified, so this review round does not change the branch.

@wenshao wenshao 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: reverse-audit chunk 5 round 4 — auditor failed twice; round 5 returned a substantive dry receipt.

Not reviewed: reverse-audit chunk 7 round 3 — receipt wording did not satisfy retirement certification; rounds 4 and 5 returned substantive dry receipts.

Not reviewed: "agent verify (round 2)" — pointed at diff lines it never opened: it made tool calls, but none of them read the diff.

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

  • docs/developers/qwen-serve-protocol.md:2175 — [review] The recovery contract omits the id_reclaimed path that skips exact-ID cleanup
  • integration-tests:1 — [review] The linked shared-channel slow-hook incident has no committed end-to-end replay
  • packages/acp-bridge/src/bridge.test.ts:12800 — [probe] The deadline metadata test accepts any numeric value
  • packages/acp-bridge/src/bridge.test.ts:13332 — [review] The per-request overdue-state test never makes both requests overdue simultaneously
  • packages/acp-bridge/src/bridge.test.ts:13347 — [review] Capacity tests do not cover pending abandonment when spare capacity remains
  • packages/acp-bridge/src/bridge.ts:13612 — [review] Shutdown waiting for abandoned newSession settlements is not regression-tested
  • packages/acp-bridge/src/bridgeErrors.ts:638 — [review] BridgeChannelQuarantinedError JSDoc understates late-success cleanup requirements
  • packages/cli/src/acp-integration/acpAgent.test.ts:2314 — [review] Agent tests do not pin checkpoints after late-resolving non-cancellable phases
  • packages/cli/src/acp-integration/acpAgent.test.ts:2362 — [review] The maximum-deadline boundary test has only millisecond-scale race slack
  • packages/cli/src/serve/server/error-response.test.ts:77 — [review] The REST route does not exercise BridgeTimeoutError mapping end to end
  • packages/core/src/config/config.test.ts:4377 — [review] Config tests do not isolate checkpoints after late-resolving initialization phases
  • packages/core/src/core/client.ts:520 — [review] Restore branches do not have focused tests for AbortSignal forwarding

Convergence: round 6 posted 1 inline comment(s), 1 of them reported for the first time. Findings keep coming back to the same files: packages/cli/src/serve/server/error-response.ts (findings in round 5; 1 more now). (Evidence: the previous round was recovered from a marker this account did not post and merged over this account's own entries, so some of those rounds may not be this account's own.) A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

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

[Critical] R6-2 [certifies-falsely] [new-surface] Abandoned restore cleanup accepts a close refusal as successful cleanup at packages/acp-bridge/src/bridge.ts:7488. A timed-out restore that later succeeds can receive {closed:false} from sessionClose; the restore path ignores that response, records cleanup as closed, clears the fence, and reopens admission while the late Session may remain alive. Validate closed === true exactly as the abandoned newSession cleanup path does. Existing sibling sessions must remain usable during quarantine and channel drain, as documented in docs/developers/qwen-serve-protocol.md:2175. Add a bridge test where late restore cleanup returns closed:false; removing the validation must make the test fail. Witness: not run — the fulfilled-response path deterministically never inspects closed.

[Critical] packages/core/src/hooks/instructionsLoadedCallback.ts:29 — [review] InstructionsLoaded hooks do not receive the initialization deadline signal (relocated from the deferral channel — a Critical is never deferred, it posts)

[Critical] packages/acp-bridge/src/bridge.ts:7569 — [probe] Restore settlement-overdue remains set after the only overdue restore settles (relocated from the deferral channel — a Critical is never deferred, it posts)

中文说明

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

未审查:reverse-audit chunk 5 round 4 — auditor failed twice; round 5 returned a substantive dry receipt。

未审查:reverse-audit chunk 7 round 3 — receipt wording did not satisfy retirement certification; rounds 4 and 5 returned substantive dry receipts。

未审查:"agent verify (round 2)"——启动 prompt 为它指定了 diff 中的行,但它从未打开:有工具调用,却没有一次读取 diff。

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

收敛情况:第 6 轮发布了 1 条行内评论,其中 1 条是首次提出。发现反复回到同一批文件:packages/cli/src/serve/server/error-response.ts(第 5 轮已出过发现,本轮又有 1 条)。(证据说明:上一轮的数据来自并非本账号发布的标记,并与本账号自己的条目合并,上述轮次中的部分可能不属于本账号。)一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

[Critical] R6-2 [certifies-falsely] [new-surface] Abandoned restore cleanup accepts a close refusal as successful cleanup at packages/acp-bridge/src/bridge.ts:7488. A timed-out restore that later succeeds can receive {closed:false} from sessionClose; the restore path ignores that response, records cleanup as closed, clears the fence, and reopens admission while the late Session may remain alive. Validate closed === true exactly as the abandoned newSession cleanup path does. Existing sibling sessions must remain usable during quarantine and channel drain, as documented in docs/developers/qwen-serve-protocol.md:2175. Add a bridge test where late restore cleanup returns closed:false; removing the validation must make the test fail. Witness: not run — the fulfilled-response path deterministically never inspects closed.

[Critical] packages/core/src/hooks/instructionsLoadedCallback.ts:29 — [review] InstructionsLoaded hooks do not receive the initialization deadline signal (relocated from the deferral channel — a Critical is never deferred, it posts)

[Critical] packages/acp-bridge/src/bridge.ts:7569 — [probe] Restore settlement-overdue remains set after the only overdue restore settles (relocated from the deferral channel — a Critical is never deferred, it posts)

— gpt-5.6-sol@954e5164 via Qwen Code /review (v0.22.2)

Comment thread packages/cli/src/serve/server/error-response.ts Outdated
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed all four round-6 Critical findings in 93c909c.

Finding Resolution
R6-1 timeout classification REST and ACP/RPC expose init_timeout only for newSession; other BridgeTimeoutError labels use the generic internal-error path.
R6-2 refused restore cleanup Late restore cleanup now requires closed:true; refusal quarantines fresh admission while live sibling sessions remain usable.
R6-3 InstructionsLoaded cancellation The initialization AbortSignal now propagates through hierarchical-memory loading into InstructionsLoaded hooks.
R6-4 restore overdue state Overdue abandoned restores are tracked per session id, so settling one request clears only its own admission block.

Verification: CLI mapper tests 46/46, ACP Bridge 822/822, Core Config and callback 594/594, HookRunner cancellation checks 3/3, relevant ESLint, Prettier, diff checks, and ACP Bridge/Core typechecks passed. The repository-wide build/typecheck remains blocked only by the known unchanged Ink selection API/type mismatch in CLI UI code; no changed-file diagnostic was emitted. One inline thread was replied to and is being resolved.

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

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Daemon session initialization continues after timeout

6 participants