feat(serve): Propagate session list cancellation - #8954
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
E2E test report
|
|
@qwen-code /triage |
|
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: 76 passed · 0 failed · 76 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:76 通过 · 0 失败 · 76 总计 Verification reportPR #8954 — feat(serve): Propagate session list cancellationVerdict: 中文摘要
Central claim + A/BCentral claim: the persisted catalog cache owns cancellation per-waiter — cancelling one waiter rejects only that caller; cancelling the last waiter aborts and synchronously detaches the physical scan so a replacement starts immediately; settlement is first-wins. A/B harness
Result: 41/41 arm-predicted checks, 0 unhandled rejections (an abort-during-settlement run would otherwise surface as one). Secondary claim 1 — cancellation reaches the physical scan. Secondary claim 2 — REST/ACP wiring cancels only the disconnecting caller. Proven by running the PR's own new tests against both source trees (copied verbatim into the base worktree; vitest aliases are worktree-relative, so the base arm resolves base source):
Every base failure is a behavioral assertion ( Reviewer Test Plan walkthrough
Findings
Not covered
MethodologyEnvironment: Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Re-run after the round-1 fixes landed — the head moved from Template ✓ — all sections present, bilingual body, reviewer test plan filled in (Before/After correctly marked N/A for daemon-internal behavior). Problem: real. #8892 merged the persisted catalog cache with single-flight shared scans; by construction, a disconnected Session List caller leaves the physical scan running to completion while its in-flight slot blocks replacement requests. That is a property of code now on Direction: aligned. This is the cancellation half of the #8892 design, in the daemon session-management area that recent CHANGELOG entries (#8891, #8892) show is under active development. REST and ACP response schemas stay unchanged; LiveTask callers deliberately remain non-cancellable. Size: core paths are touched ( Approach: scope fits the goal. A per-load controller with a per-waiter registry, first-wins settlement, last-waiter abort with synchronous detach, and cooperative checkpoints at every site where a swallowed abort would surface as a wrong result. That is the same shape I'd propose from the problem statement alone; the obvious simpler alternative — wiring the first caller's signal straight to the shared loader — would let one disconnect cancel every other waiter, which is exactly what the description rejects. No drive-by edits; the doc change is this feature's design doc. Risk: no Stage 1e high-risk-path matches. Moving on to code review. 🔍 中文说明Round-1 修复后的 re-run —— head 从 模板 ✓ —— 各节齐全、中英双语,reviewer 测试计划已填写(daemon 内部行为,Before/After 正确标注 N/A)。 问题:真实存在。 #8892 合入了带 single-flight 共享扫描的持久化目录缓存;按其构造,断开的 Session List 调用方会让物理扫描一直跑完,且其 in-flight 槽位会挡住替换请求。这是已在 方向:对齐。 这是 #8892 设计的取消那一半,处于近期 CHANGELOG 条目(#8891、#8892)表明正在活跃开发的 daemon 会话管理领域。REST 与 ACP 响应 schema 保持不变;LiveTask 调用方有意保持不可取消。 规模: 触及核心路径( 方案:范围与目标相称。 按 load 持有 controller、按 waiter 注册、first-wins 结算、最后一个 waiter 中止并同步脱离,外加在每个"吞掉中止就会变成错误结果"的位置设置协作检查点。这正是我只看问题陈述就会提出的形态;显然更简单的替代 —— 把第一个调用方的 signal 直接接到共享 loader —— 会让一次断开取消所有其他等待者,也正是 PR 描述所否决的方案。无顺手改动;文档改动即本特性的设计文档。 风险: 无 Stage 1e 高风险路径命中。进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewI wrote down my own design before reading the diff — waiter registry on the shared load, load-owned
The round-1 delta ( One open item carried over from the external review rounds, R2-7: cancellation stops at the No blocking findings, no AGENTS.md violations. sequenceDiagram
participant P1 as REST or ACP caller
participant P2 as Session list route
participant P3 as Cache waiter
participant P4 as Cache load
participant P5 as Persisted scan
P2->>P3: attach waiter with caller signal
P3->>P4: join the in-flight load, waiter count plus 1
P4->>P5: loader runs with the load-owned signal
Note over P1,P3: caller disconnects - only that waiter rejects
P3->>P4: last waiter cancels, count reaches 0
P4->>P5: abort the load controller, detach the slot
Note over P4: the next request starts a fresh scan immediately
Files changed (16 of 16 shown)
Test evidence (the PR's own CI, fetched via API — PR code was not executed in this run)CI on the reviewed commit is fully settled: every
One row per check name (latest run); merge-queue-only skipped checks listed in prose above. / 每个检查名一行(取最新一次运行),merge-queue-only 的 skipped 检查见上文说明。 The unit coverage is substantive: the cache tests drive the real 中文说明代码审查:读 diff 前我先独立写下自己的设计(共享 load 上的 waiter 注册表、load 自持的 AbortController、first-wins 结算、最后一个 waiter 中止并同步脱离、扫描链路上的协作检查点),本 PR 正是这一形态。随后专门去找这类设计最难处理的竞态,每一种都被处理且有专门测试钉住:first-wins 结算(甚至有在投递中途从 snapshot getter 内部触发中止的测试);loader 微任务前取消不会启动 loader、已中止的调用方同步拒绝且不建槽位;脱离与替换 load 的身份隔离(旧 load 迟到的 resolve 与 reject 两个方向都无法安装、清除或覆盖替换 load,且 waiter 的 then 处理器在结算后仍挂在 load promise 上,脱离的 load 拒绝不会变成 unhandled rejection);中止永远不会变成错误结果 —— 带 ENOENT code 的中止理由不会被吞成空目录,取消也不会变成"会话不存在"、丢失 sidecar、liveMergeFailed 回退或假的 preflight,每个吞错点都先 throwIfAborted 且都有测试。无 signal 路径完全保留(有 setImmediate spy 测试钉住),全部既有消费方 —— CLI resume/branch、ACP agent、live-task service、worktree 启动/退出、session-id admission 及其他 readLines/readRuntimeStatus 调用 —— 都走未改动的路径;persistedSessionListCache.lookup 的唯一消费方就是目录 loader 本身。Round-1 差异(14257b7 → 9e835e2)仅为测试:REST 与缓存层的最后 waiter 物理中止覆盖、双 ACP 连接销毁隔离、core 读取的判别性覆盖、scan leader 与 single-flight waiter 两侧的 scan_duration_ms 钉住 —— 已通过 compare API 确认无生产代码改动。外部评审遗留项 R2-7:取消止步于 SessionOrganizationService.readSnapshot 边界,改为前后设置 signal 检查 —— 与文档化设计一致,但是否把取消扩展进 organization service 明确是维护者决定,已在结论评论中标出而非默认吸收。无阻塞性问题,无 AGENTS.md 违规。时序图与改动文件概览见上。 测试证据(通过 API 读取 PR 自己的 CI —— 本次运行未执行 PR 代码):审查提交上的 CI 已完全落定:所有 pull_request 工作流运行完成,执行的检查全部为绿 —— Linux 单元测试、Serve A/B(响应相对 base 无漂移)、含真实 daemon E2E 的 SDK Java、双平台 Desktop Shell 构建、web-shell smoke、precheck。三个检查按设计跳过而非本 PR 所致:macOS/Windows 单元测试与 CLI 集成测试在 ci.yml 中是 merge-queue-only 任务,PR 进入队列时才会运行。被取消的 route 检查是 re-trigger 产生的机器人命令路由任务,不属于 PR CI。单元测试覆盖是实质性的:缓存测试驱动真实的 PersistedSessionListCache(仅 stub listSessions),两个路由测试通过 createServeApp 发起真实 HTTP 请求并中途 abort,ACP 测试走真实传输层。沙箱验证是补足 CI 无法覆盖部分的通道 —— 在真实 daemon 上断开最后一个 waiter 是否确实中止物理扫描、替换冷扫描是否完整返回目录而无 500/部分响应/ACP 错误帧。本线程的 /verify 运行已在 14257b7 上以 76/76 断言通过并带完整 A/B 证据(缓存语义 41/41、核心扫描 13/13、PR 自己的测试移植到 base 后按预期变红),而其后的唯一提交仅为测试文件,因此该证据逐字节适用于当前 head 的生产代码;本次运行派发的 verify 阶段正在执行,完成后会单独发布报告。未验证:作者的 1,200 会话 E2E 为自报且仅在 macOS 执行;Windows/Linux E2E 作者标记为范围外(merge queue 覆盖平台套件)。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, careful concurrency work, now on fully settled evidence: every CI check that runs is green on this head, the sandboxed verification applies byte-for-byte (the round-1 commit is tests-only), and every race in the waiter design is pinned by a test. Withholding the fifth point for the two things a human owns: the contested R2-7 boundary, and the author's behavioral E2E being macOS-only. Stepping back: this is the second half of #8892 — a gap the single-flight cache creates by construction, closed by the minimal honest shape. I tried to find a materially simpler design (per-call aborts, one shared caller signal, detach without abort) and each one either starves sibling waiters or leaves the physical scan running — the very thing being fixed. Every hunk earns its place; deleting any of them reopens one of the swallow-an-abort holes the tests pin. The round-1 push added exactly the missing pins — last-waiter physical abort, two-connection ACP isolation, Verdict: approve. No
中文说明置信度:4/5 —— 干净、细致的并发工作,且证据已完全落定:该 head 上所有执行的 CI 检查为绿,沙箱验证逐字节适用(round-1 提交仅为测试),waiter 设计中的每一种竞态都有测试钉住。保留最后 1 分给两件由人决定的事:有争议的 R2-7 边界,以及作者的行为性 E2E 仅在 macOS 执行。 退一步看:这是 #8892 的下半场 —— single-flight 缓存与生俱来的缺口,用最小且诚实的形态补上。我尝试找更简单的设计(按调用方中止、单一共享调用方 signal、只脱离不中止),每一种要么饿死其他等待者,要么放任物理扫描继续跑 —— 而那正是要修的问题。每个改动块都有其价值:删掉任何一处都会重新打开某个被测试钉住的"吞掉中止"的洞。Round-1 推送恰好补上了缺失的钉子 —— 最后 waiter 物理中止、双 ACP 连接隔离、single-flight 两侧的 scan_duration_ms —— 此外别无其他;六个月后读 attachWaiter 的结算逻辑,能看出作者清楚自己应对的是哪些竞态。作者目前有不少 open PR,但这一个靠自身证据站得住。 结论:批准。该 head 上没有在途的 pull_request 运行,执行的检查全部为绿,因此本次运行将批准钉在
— Qwen Code · qwen3.8-max Reviewed at |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 4 scenario(s). — Qwen Code · serve A/B |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
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): You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; chunk 4: none — all checks I started completed within budget..
中文说明
已审查。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;chunk 4:none — all checks I started completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.10)
wenshao
left a comment
There was a problem hiding this comment.
Not reviewed: cross-file tracing — the agent failed to return twice.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and local verification used Node 24.18.1.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and Windows was not tested locally.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
未审查:cross-file tracing — the agent failed to return twice。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and local verification used Node 24.18.1。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and Windows was not tested locally。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— gpt-5.6-sol via Qwen Code /review (v0.21.10)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
[codex] Review round 1 handled in
Verification: focused Core and CLI Vitest suites passed; repository build, typecheck, and lint passed. Two consecutive broad diff audits found no further actionable issue. Resolved 19/19 review threads after replying to each. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and not covered locally.
Not reviewed: build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and not covered locally.
Not explored to full depth (tool budget reached): You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed; I did not run live deletion probes, having verified the candidate paths statically against the exact post-change sou…; You are review agent reverse-audit — Reverse audit agen...: none — all checks above completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all planned checks completed within budget.; You are review agent reverse-audit — Reverse audit agen...: none — all checks I started were completed within budget..
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:build-and-test — Test (macos-latest, Node 22.x) was skipped in CI and not covered locally。
未审查:build-and-test — Test (windows-latest, Node 22.x) was skipped in CI and not covered locally。
未探索到全部深度(达到工具调用预算):You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed; I did not run live deletion probes, having verified the candidate paths statically against the exact post-change sou…;You are review agent reverse-audit — Reverse audit agen...:none — all checks above completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all planned checks completed within budget.;You are review agent reverse-audit — Reverse audit agen...:none — all checks I started were completed within budget.。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.10)
| readOptions.signal?.throwIfAborted(); | ||
| const snapshot = await organizationService.readSnapshot(); | ||
| readOptions.signal?.throwIfAborted(); |
There was a problem hiding this comment.
[Critical] R2-7: Blocker re-check — the open Critical from @wenshao (R1-7) still stands at this commit. The organized path checks readOptions.signal?.throwIfAborted() immediately before and after organizationService.readSnapshot(), but passes no signal into the read itself; SessionOrganizationService.readSnapshot()/readStore() accept no AbortSignal, so the store file read + JSON parse + normalization still run to completion after a disconnect. The round-1 reply declined the change citing the approved design (single bounded store read); a reply alone does not retire a blocker, and the mechanism is present in the code as it stands. — Failure scenario: a client disconnects while an organized request is reading the organization store → readSnapshot() continues the un-signaled file read, parsing, and normalization until it returns, so the aborted request still consumes I/O and CPU for the whole organization read.
Suggested fix: propagate an optional AbortSignal through readSnapshot()/readStore() (pass it to the store file read), or reach an explicit maintainer decision on the declined scope in the existing thread.
中文说明
R2-7: 阻塞项复查 —— @wenshao 的未解决 Critical(R1-7)在当前提交仍然存在。organized 路径在 organizationService.readSnapshot() 前后都检查了 readOptions.signal?.throwIfAborted(),但没有把 signal 传入读取本身;SessionOrganizationService.readSnapshot()/readStore() 不接受 AbortSignal,因此断开连接后 store 文件的读取 + JSON 解析 + 规范化仍会执行到结束。第一轮的回复以已批准的设计(单次有界的 store 读取)为由拒绝了该修改;仅有回复并不能解除阻塞项,该机制在当前代码中依然存在。
失败场景:客户端在 organized 请求读取 organization store 时断开 → readSnapshot() 继续执行未绑定 signal 的文件读取、解析和规范化直到返回,已取消的请求在整个 organization 读取期间继续消耗 I/O 与 CPU。
建议修复:为 readSnapshot()/readStore() 增加可选 AbortSignal(传给 store 文件读取),或在现有讨论串中就拒绝的范围与维护者达成明确决定。
— qwen3.8-max via Qwen Code /review (v0.21.10)
There was a problem hiding this comment.
Thanks — this is a scope disagreement rather than a branch defect. The approved plan explicitly keeps the single small-file organization snapshot read outside the cancellable API and requires signal checks immediately before and after it; the current code follows that contract. Please have a maintainer confirm whether to override that approved boundary before expanding SessionOrganizationService.
| req.once('aborted', onRequestAborted); | ||
| res.once('close', onResponseClosed); | ||
| if (req.aborted || res.destroyed) onRequestAborted(); |
There was a problem hiding this comment.
[Suggestion] R2-2: The upfront req.aborted || res.destroyed pre-check — the only thing that cancels the controller when the client was already gone before the listeners attached — is not exercised by any test. Probe-verified: deleting the line left all 1252 serve tests green; no test references req.aborted/res.destroyed, and the trusted-secondary preflight test aborts mid-scan, after the handler already passed this check. — Failure scenario: a client disconnects while earlier middleware (auth, workspace-runtime resolution) is still being awaited; req's one-shot 'aborted' event has already fired and, if res 'close' fired too, neither re-armed listener ever triggers → deleting the pre-check lets a full persisted catalog scan run to completion for a dead connection, with the suite staying green.
Suggested fix: add a REST test that destroys/aborts the request before the handler's listeners attach (e.g. abort the supertest request immediately after issuing it, with a delayed first listSessions call) and asserts the scan signal aborts / no physical scan completes.
中文说明
R2-2: 前置的 req.aborted || res.destroyed 预检查 —— 客户端在监听器挂载前就已离开时唯一能取消 controller 的路径 —— 没有任何测试覆盖。探针验证:删除该行后全部 1252 个 serve 测试仍全绿;没有测试引用 req.aborted/res.destroyed,且 trusted-secondary preflight 测试是在扫描中途(handler 已通过该检查之后)才 abort。
失败场景:客户端在前面的中间件(鉴权、workspace-runtime 解析)仍在 await 时断开;req 的一次性 'aborted' 事件已经触发,若 res 的 'close' 也已触发,重新挂载的两个监听器都不会再触发 → 删除该预检查会让完整的持久化目录扫描为一个已死连接执行到底,而测试套件依旧全绿。
建议修复:新增 REST 测试,在 handler 监听器挂载前销毁/abort 请求(例如发出 supertest 请求后立即 abort,并延迟第一次 listSessions 调用),断言扫描 signal 被中止/物理扫描未完成。
— qwen3.8-max via Qwen Code /review (v0.21.10)
There was a problem hiding this comment.
Thanks — not taking this one. An immediate Supertest abort would not prove the pre-check because the route may never run; a valid test needs contrived middleware, and this handler has no await before listener installation. That complexity is not justified as a Critical fix after the current review depth.
|
Review round 2 triage on
No code change was made. Seven deferred threads were resolved; the two disputed threads remain open. The unrelated Linux |
yiliang114
left a comment
There was a problem hiding this comment.
Reviewed at 9e835e2 — cancellation propagation through the daemon session-list path (REST disconnect + ACP connection teardown → catalog waiter cancellation, per-load AbortController in the persisted catalog cache).
Correctness — no P0/P1 found:
- Clean waiter/load signal separation: caller signals attach only to their own waiter promise (
attachWaiter); the physical load owns its controller and is never directly combined with caller signals. Abort listeners are removed on settlement ({once:true}+ explicitremoveEventListener) — no leaked listeners, no timers added. - First-wins settlement (
load.settled+ per-waiter flag) prevents a late caller abort from replacing a completed scan; detached/stale loads cannot install snapshots (identity + generation + aborted guards) nor clear a replacement load — both race directions are test-covered. - Last-waiter cancellation aborts and synchronously detaches the load so a replacement scan starts immediately; the detached
managedrejection is still handled by the already-attached waiter handlers, so no unhandled-rejection path even if a loader ignores cancellation. - Every catch-swallow site re-checks
signal.throwIfAborted()before falling back (ENOENT→empty incl. an ENOENT-coded abort reason, sidecar→null, sessionExists→false, live-merge→liveMergeFailed, readLines/readRuntimeStatus/readWorktreeSession), so cancellation cannot be misreported as missing data. - The 128-entry setImmediate yield in
listSessionsonly triggers when a signal is passed; the signal-less CLI/LiveTask path is verified unchanged. - Route wiring removes both
req 'aborted'/res 'close'listeners in finally; the ACP dispatch silently returns when the connection signal is aborted, avoiding buffered error frames on destroyed connections (asserted viabufferedConnectionFrames).
Tests: ~950 new lines covering single/last-waiter cancel, detach+replace, pre-aborted lookup, null abort reason, cache-hit cancellation, REST/ACP disconnect integration, worktree enrichment, numeric pagination, and core signal plumbing.
CI at head sha: Test (ubuntu-latest, Node 22.x) ✅, web-shell E2E smoke ✅, Desktop Shell (ubuntu/windows) ✅; cancelled route runs are fork-routing no-ops; mac/win/integration skipped as expected for fork PRs.
Non-blocking nits (P3):
- The
signal ? sessionExists(id, {signal}) : sessionExists(id)ternary is repeated in three places; always passing the options object would be equivalent since it defaults to{}. req.aborted/'aborted'is legacy-leaning on newer Node, but fine for the current HTTP/1 serve stack.
LGTM.
|
@qwen-code /triage |
|
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: 122 passed · 0 failed · 122 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:122 通过 · 0 失败 · 122 总计 Verification report<!-- qwen-triage:verify --> Sandboxed verification: ✅ passed — merge-ready (agent verdict) - follow-up round at new head 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: 122 passed · 0 failed · 122 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中对该 PR 的新 head( 脚本断言:122 通过 · 0 失败 · 122 总计 Verification reportPR #8954 — feat(serve): Propagate session list cancellation (follow-up round)Verdict: 中文摘要
Previous-finding status table (follow-up round)
No new findings this round. (The two PR tests that are green on base — Central claim + A/BCentral claim: the persisted catalog cache owns cancellation per-waiter — cancelling one waiter rejects only that caller with its original reason; cancelling the last waiter aborts the physical load and synchronously detaches it so a replacement starts immediately; settlement is first-wins; a detached load's late settle or rejection cannot install or clear anything; no-signal callers remain non-cancellable waiters. A/B harness
Secondary claim 1 — cancellation reaches the physical scan. Secondary claim 2 — REST/ACP wiring cancels only the disconnecting caller. Proven by running the PR's own new tests against both source trees (copied verbatim into the base worktree; vitest aliases are worktree-relative, so the base arm resolves base source):
Witness: Reviewer Test Plan walkthrough
FindingsNone new. The two carried informational observations stand (see status table). No blocking issue. Not covered
MethodologyEnvironment: Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.21.11. |








What this PR does
This PR propagates request cancellation through daemon Session List reads while preserving the persisted catalog cache introduced by #8892. Shared organized and metadata scans now track independent waiters: cancelling one REST or ACP caller leaves other REST, ACP, and LiveTask waiters running, while cancelling the last waiter aborts and detaches the physical scan so a replacement request can start immediately. Cancellation also reaches numeric pagination, JSONL reads, runtime-status and worktree-sidecar enrichment, persisted-existence checks, and the cooperative directory scan.
Why it's needed
Disconnected Session List clients previously left expensive persisted-session scans running to completion, which could waste daemon resources and delay later catalog work. Directly wiring a caller signal to the shared loader would instead let one client cancel every consumer, so the cache needs waiter-aware cancellation ownership and late-load isolation.
Reviewer Test Plan
How to verify
Start two identical organized Session List requests and disconnect one; the remaining request should return the complete catalog from one physical scan. Disconnect every cancellable waiter; the physical scan should abort, and the next request should start a fresh scan before the following request hits the existing two-second TTL. Verify numeric pagination, trusted-secondary persisted preflight, ACP connection destruction, and cancellation during JSONL, runtime-status, worktree-sidecar, and project-membership reads return no partial data, HTTP 500, ACP error frame, or cancellation error log.
Evidence (Before & After)
N/A — daemon-internal behavior with unchanged REST and ACP response schemas.
Tested on
Environment (optional)
macOS local Node.js 22-compatible workspace; isolated
qwen serveE2E with 1,200 persisted sessions and package-level Vitest coverage.Risk & Scope
Linked Issues
References #8892.
中文说明
此 PR 的作用
此 PR 在保留 #8892 引入的持久化目录缓存基础上,将请求取消传播到 daemon Session List 读取。共享的 organized 和 metadata 扫描现在跟踪独立 waiter:取消单个 REST 或 ACP 调用方不会影响其他 REST、ACP 或 LiveTask waiter;最后一个 waiter 取消时会中止并同步脱离物理扫描,使替代请求可以立即启动。取消还会传播到数字分页、JSONL 读取、runtime status 与 worktree sidecar 补充、持久化存在性检查以及协作式目录扫描。
为什么需要
Session List 客户端断开后,昂贵的持久化会话扫描此前仍会运行到结束,浪费 daemon 资源并可能延迟后续目录工作。若将调用方 signal 直接连接到共享 loader,单个客户端又会取消所有消费者,因此缓存需要 waiter 感知的取消所有权和晚到 load 隔离。
Reviewer 测试计划
如何验证
启动两个相同的 organized Session List 请求并断开其中一个;剩余请求应通过一次物理扫描返回完整目录。断开所有可取消 waiter 后,物理扫描应中止,下一请求应启动新的扫描,随后请求命中现有两秒 TTL。验证数字分页、可信 secondary 的 persisted preflight、ACP connection destroy,以及 JSONL、runtime status、worktree sidecar 和项目归属读取期间的取消都不会返回部分数据、HTTP 500、ACP error frame 或取消错误日志。
证据(Before & After)
N/A — daemon 内部行为,REST 和 ACP 响应 schema 未变化。
测试平台
环境(可选)
macOS 本地 Node.js 22 兼容工作区;使用 1,200 个持久化 session 的隔离
qwen serveE2E,并运行了包级 Vitest。风险与范围
关联 Issue
参考 #8892。