Skip to content

fix(serve): preserve session creation failure diagnostics - #12331

Merged
callmeYe merged 3 commits into
mainfrom
codex/11944-creation-diagnostics
Sep 23, 2026
Merged

callmeYe merged 3 commits into
mainfrom
codex/11944-creation-diagnostics

Conversation

@callmeYe

@callmeYe callmeYe commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Preserves correlated diagnostics when standalone session creation fails, including the original in-memory cause, failed phase, dispatch state and cleanup outcome. The target child ID remains distinct from a parent-validation error's ID. A dedicated service-boundary record covers HTTP and direct child/side-task callers, while source-persistence diagnostics distinguish private negative acknowledgements, legacy or malformed replies, local timeouts, transport closure and RPC rejection.

Why it's needed

A failure before dispatch and a failure to confirm source persistence currently produce the same rollback response and warnings without the collection request's session ID. Operators cannot distinguish those paths, and rollback or quarantine can discard the initiating cause. This change makes those failures diagnosable without changing public HTTP codes, messages, retry headers or sourcePersisted, and without logging raw causes or request payloads.

Reviewer Test Plan

How to verify

  • Trigger a creation failure before dispatch and an unconfirmed source write through the standalone HTTP route. Both should retain the existing HTTP 500 rollback body and Retry-After: 5, while each produces one dedicated diagnostic with the target session ID and its distinct phase/dispatch state. No prompt is admitted.
  • Trigger direct child creation against a missing or invalid parent. The diagnostic should retain the validated target child ID, include only a validated parent as the related ID, and preserve the existing public error behavior.
  • Fail cleanup after a creation failure, or fail both attempts to commit a binding. The original exception chain should remain in memory, the first failed phase should remain identifiable, and cleanup should distinguish completed quarantine from an unknown outcome. Healthy siblings must not be killed during verified rollback.
  • Exercise unavailable recording, an unconfirmed write, an old child's negative acknowledgement, malformed acknowledgement, actual timeout/transport closure and RPC rejection, including cold restore and live source backfill. The boolean contract should remain unchanged and diagnostics should contain only safe classifications. Secret sentinels in causes, source payloads and prompts should be absent from the new logs, trace arguments and HTTP output.

Evidence (Before & After)

Before: deterministic real route + service + serializer fault injection reproduced identical uncorrelated warnings and a lost cause for distinct creation failures. The installed global CLI is too old for this route (404), so that binary is not claimed as a supported baseline.

After: focused regression tests verify exact HTTP compatibility, distinct correlated diagnostics, cause-chain identity, cleanup failure handling, direct-child identity, throwing sinks and privacy. The source transport tests use an actual unanswered RPC/closed channel rather than matching remote exception text. The initial implementation passed 2,055 focused tests on macOS. The CR follow-up additionally passed 177 focused tests, build, typecheck, bundle and changed-file lint/format checks, and preserves original exception stacks/types while limiting cause-free projection to creation errors. Two complete local diff self-audit passes found no remaining actionable issue; the local review used medium effort and does not replace maintainer review.

Tested on

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

Environment (optional)

Node.js 22.17.0. Deterministic local fault injection without model invocation; the compiled candidate completed real creation with sourcePersisted:true, detach and deletion without errors. Failure acceptance also includes actual source-RPC timeout and child transport closure against the bundle, with classifications read from the real daemon log file; details and sibling-survival boundaries are in the follow-up test report.

Risk & Scope

  • Main risk or tradeoff: incorrect attribution or accidental cause serialization; diagnostics use fixed fields and sanitized telemetry errors, with targeted regression coverage.
  • Not validated / out of scope: live provider inference, deployed incident root cause and Windows/Linux execution. This is PR 1 only and does not depend on PRs 2–4.
  • Breaking changes / migration notes: none. Only the private failed source acknowledgement gains an optional reason; old children and clients retain existing behavior.

Design: English · 简体中文.

Linked Issues

Refs #11944. This PR does not close the four-part tracker.

中文说明

本 PR 的改动

保留 standalone 会话创建失败的关联诊断,包括内存中的原始 cause、失败阶段、派发状态和清理结果。目标 child ID 与父会话校验错误中的 ID 分开记录。服务边界的专用记录覆盖 HTTP 和直接 child/side-task 调用;来源持久化诊断区分私有负确认、旧版或非法响应、本地超时、transport 关闭和 RPC 拒绝。

为什么需要

派发前失败和来源持久化未确认目前返回相同的 rollback 响应,集合请求的 warning 缺少 session ID。运维无法区分两条路径,回滚或隔离还可能丢失最初 cause。本改动让这些失败可诊断,同时保持公共 HTTP code、文案、重试响应头和 sourcePersisted 不变,不记录原始 cause 或请求载荷。

Reviewer 测试计划

如何验证

  • 通过 standalone HTTP 路由触发派发前失败和来源写入未确认。两者仍应返回既有 HTTP 500 rollback 正文及 Retry-After: 5,但各产生一条带目标 session ID 和不同阶段/派发状态的专用诊断,不准入 prompt。
  • 对缺失或非法父会话触发直接 child 创建。诊断应保留已校验的目标 child ID,仅将已校验的父 ID 作为关联 ID,公共错误行为不变。
  • 在创建失败后让清理失败,或让两次绑定提交都失败。原始异常链应保留在内存,最初失败阶段仍可识别,清理结果区分已完成隔离和未知结果。已确认回滚不能关闭健康兄弟会话。
  • 覆盖 recording 不可用、写未确认、旧 child 负确认、非法确认、真实超时/transport 关闭、RPC 拒绝,以及冷恢复和在线来源补全。Boolean 契约应保持不变,诊断只含安全分类。Cause、来源载荷和 prompt 中的秘密标记不能出现在新增日志、trace 参数和 HTTP 输出中。

修改前后证据

修改前:真实 route + service + serializer 的确定性故障注入复现了不同创建失败产生相同、无关联 ID 的 warning,且 cause 丢失。全局 CLI 过旧,该路由返回 404,因此不将该二进制作为支持能力的基线证据。

修改后:聚焦回归测试验证精确 HTTP 兼容、不同关联诊断、cause 链引用身份、清理失败、直接 child 身份、抛错输出端和隐私。来源 transport 测试使用真正未响应的 RPC/关闭的 channel,不匹配远端异常文本。初版在 macOS 上通过 2,055 项聚焦测试;CR 修复另外通过本轮 177 项测试、build、typecheck、bundle 和改动文件 lint/格式检查,并保留原始异常栈与类型,只对创建错误做无 cause 投射。两轮完整本地 diff 自审无待处理问题;本地 review 使用 medium effort,不替代维护者评审。

测试平台

OS 状态
🍏 macOS ✅
🪟 Windows ⚠️ 未在本地测试
🐧 Linux ⚠️ 未在本地测试

环境

Node.js 22.17.0。确定性本地故障注入,无模型调用;编译产物完成真实创建并返回 sourcePersisted:true,随后 detach 和删除均无错误。失败验收还包括对 bundle 注入真实来源 RPC 超时及 child transport 关闭,并从实际 daemon 日志文件读取分类;详情和兄弟会话存活的证据边界见后续测试报告。

风险与范围

  • 主要风险或权衡:错误归因或意外序列化 cause;使用固定诊断字段、脱敏 telemetry Error,并提供定向回归覆盖。
  • 未验证或范围外:真实 provider 推理、部署事故根因、Windows/Linux 执行。仅实现 PR 1,不依赖 PR 2–4。
  • 不兼容变更或迁移:无。仅私有来源失败确认增加可选 reason,旧 child 和客户端行为保持兼容。

设计:English · 简体中文。

关联 Issue

Refs #11944。本 PR 不关闭四项 tracker。

@callmeYe

Copy link
Copy Markdown
Collaborator Author

Deterministic creation-diagnostics validation

Verified on macOS / Node.js 22.17.0, without a model call.

Layer Result
Baseline Real Express route + standalone service + HTTP serializer reproduced identical rollback responses, missing warning session correlation, and a lost cause.
Candidate HTTP compatibility Both injected failure paths retain the exact HTTP 500 body and Retry-After: 5, while dedicated records distinguish pre-dispatch from source persistence and correlate to the target UUID. No initial prompt is admitted.
Cause / cleanup / identity Original nested cause identity survives wrapping and failed cleanup; completed quarantine and unknown containment are distinct. Direct-child diagnostics keep target and validated parent IDs separate; missing/null/invalid parents are rejected. Verified rollback does not kill healthy siblings.
Private source acknowledgement Recording unavailable, write not confirmed, legacy negative acknowledgement, malformed reply, actual local timeout, transport closure and RPC rejection are classified without raw payloads. Cold load/resume and live source backfill preserve the boolean contract.
Privacy / sinks Secret sentinels in causes, source payloads and prompts are absent from the diagnostic/HTTP projections; telemetry receives cause-free Errors. Throwing diagnostic sinks do not replace the creation failure.
Compiled candidate node dist/cli.js completed real no-prompt creation (HTTP 200, sourcePersisted:true), exact-client detach (204), and exact-session deletion with no errors or pending cleanup. The daemon was stopped.
Independent verification Test engineer reran 25 selected diagnostic/transport/acknowledgement cases against final commit 9a9279376153dde4780025c0f44a4bf4d8231882: all passed.
Local checks Build, typecheck, bundle, changed-file ESLint and Prettier checks passed.
Regression suites 120 standalone service + 26 routes + 53 serializer + 55 child tool + 58 logger + 769 ACP + 974 bridge = 2,055 passed.

Evidence boundaries: the installed global CLI (0.18.5-preview.0) lacks this route and returned 404; it is not claimed as a supported daemon baseline. Deterministic failure acceptance uses the real source route/service/serializer with injected transport/storage boundaries. Bridge timeout/closure acceptance uses the real in-memory ACP transport. This does not establish a deployed incident's root cause, a real filesystem failure, provider inference behavior, or Windows/Linux acceptance.

中文验证说明

macOS / Node.js 22.17.0,无模型调用。最终编译产物完成真实无 prompt 创建(200、sourcePersisted:true)、精确 client detach(204)、精确 session 删除(无错误和待清理项),测试 daemon 已停止。测试代理针对最终提交 9a9279376153dde4780025c0f44a4bf4d8231882 独立复跑 25 个选定用例,全部通过。基线真实 route/service/serializer 故障注入确认:不同创建失败返回相同 rollback 响应,warning 缺少 session 关联且 cause 丢失。候选保持精确 HTTP 500 正文和 Retry-After: 5,新增诊断区分派发前/来源持久化阶段并关联目标 UUID,不准入首轮 prompt。

验证了嵌套 cause 身份、清理失败与隔离结果、child/parent 身份分离、缺失/null/非法父 ID 拒绝、回滚不关闭健康兄弟会话,以及 recording 不可用/写未确认/旧版负确认/非法响应/真实本地超时/断连/RPC 拒绝。冷恢复和在线来源补全的 boolean 契约不变。秘密标记不进入新增诊断和 HTTP 投影,telemetry 使用无 cause Error,诊断输出端抛错不替换创建失败。

Build、typecheck、bundle、改动文件 ESLint/Prettier 和 2,055 项回归测试通过。全局旧版 CLI 不支持该路由(404),失败验收使用真实源码组合及注入的 transport/storage 边界;bridge 超时/断连使用真实内存 ACP transport。不据此宣称部署事故根因、真实文件系统故障、provider 推理或 Windows/Linux 验收。

@elizabethbrunette21

Copy link
Copy Markdown
Contributor

我看了 session creation failure 的 error propagation 和 source persistence classification,这个设计把 phase / dispatch state / cleanup outcome 分开记录,避免了 rollback 后丢失 root cause。一个小问题是未来如果新增更多 wrapper error,cause extraction 是否考虑统一封装,避免新路径遗漏。

@callmeYe

Copy link
Copy Markdown
Collaborator Author

Fixed in e3a6643654677b843cd76157f318d9028a2c85da. Addressed the actionable findings from the code review and the defer explanation:

  1. Telemetry fidelity and scope: the cause-free projection now applies only to errors carrying a creation diagnostic, and preserves the original safe name, code and stack. Non-creation errors (including deletion/recovery) pass through as the same object. I checked the installed OTel recorder: it reads code/name/message/stack and does not traverse cause, so the projection is documented as narrow defence in depth rather than a requirement of today's recorder. Both regression cases failed against the previous head and now pass; tests also verify the creation cause/private payload are omitted without mutating the original error.
  2. Phase coverage: added runtime-acquisition and post-persistence durable-validation fault cases; extended the existing scheduled child model-failure rollback case to assert model_selection, child/parent correlation, dispatch and cleanup. These tests exercise the actual failure paths and continue asserting no prompt admission.
  3. Documented scope: both design languages now limit the source diagnostic claim to operations through the shared source-persistence helper. Branched-session writes and default persistence bypass it and retain their existing behavior; this follow-up does not widen into those paths.
  4. Bundled verification: executed actual source-RPC timeout and child-transport closure against dist/cli.js using an isolated process preload that intercepts only the selected session's child RPC. The daemon's own timeout and channel-close handling perform classification; no diagnostic value is injected. Actual daemon log files contain rpc_timeout / transport_closed and the correlated source_persistence / dispatched / rolled_back creation records. Both responses retain HTTP 500 rollback and Retry-After: 5. No prompt RPC or model call is involved. A healthy sibling remains live after timeout (detach 204). Killing the shared child necessarily removes both live registrations; the closure case verifies the sibling remains durably readable (GET 200) and deletes cleanly, not live attachment survival.

Validation: build, typecheck, bundle, changed-file ESLint/Prettier and 177 focused tests passed; two clean follow-up diff audit passes. Binary fault-injection evidence is separate from source-level regression coverage and does not claim an external provider or real storage-device fault.

The capacity-specific safe reason is left as an optional diagnostic enhancement; the existing public capacity payload is unchanged. A generalized wrapper-unwrapping abstraction is deferred until another concrete wrapper requires it, to keep this PR scoped.

中文说明

已处理有效评审意见:仅对带创建诊断的错误做无 cause 投射,保留原始安全的 name/code/stack;删除、恢复等其他错误保持原对象。已检查本地 OTel 实现,它不遍历 cause,因此文档将该投射明确为窄范围纵深防护。两条回归用例在旧 head 失败、修复后通过,并断言 cause/私有载荷不进入投射且原错误未被修改。

新增 runtime 获取失败、持久化确认后的 durable validation 失败测试,并在既有定时 child 模型失败回滚用例中增加 model_selection、父子关联、派发和清理结果断言。中英文档同步收窄来源诊断覆盖声明,明确 branch/default 绕过 helper 的路径不在此次改动内。

对真实 dist/cli.js 用隔离 preload 仅拦截指定 session 的 child RPC,分别造成真实等待超时和子进程 transport 关闭;分类由 daemon 原有处理产生,没有注入诊断结果。真实 daemon 日志文件记录 rpc_timeout/transport_closed,以及关联的 source_persistence/dispatched/rolled_back;两次 HTTP 均保持 500 rollback 和 Retry-After:5,没有 prompt RPC 或模型调用。超时场景的健康兄弟会话保持在线(detach 204);杀死共享 child 必然移除两者的在线注册,断连场景只证明兄弟会话持久数据仍可读(GET 200)且删除成功,不宣称在线连接存活。

Build、typecheck、bundle、改动文件 ESLint/Prettier 和本轮 177 项测试通过,两轮补丁自审干净。二进制故障注入与源码回归分开报告,不宣称外部 provider 或真实存储设备故障验收。容量专用 reason 保留为可选增强,公共 capacity 载荷不变;在出现另一个实际 wrapper 需求前,不引入通用解包抽象。

@wenshao

wenshao commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification round 1 (local, Linux aarch64)

Verdict: merge-ready — 65/65 scripted assertions passed, 0 failed. Verified head e3a6643654677b843cd76157f318d9028a2c85da (merged conflict-free onto main@615fcfcd0c97f451be9d15719c0503613957e13e; all measurements taken on the merged tree vs current main).

中文摘要

结论:merge-ready(可合并) — 65/65 项脚本断言全部通过,0 失败。验证对象为 PR head e3a6643,并无冲突合并到当前 main(615fcfcd)后在与 main 的纯 A/B 对照下完成全部测量(本 PR 的修改是唯一变量)。

A/B 结论(核心声明成立):6 个会话创建失败场景(运行时获取失败、派发前 spawn 失败、来源持久化未确认、父会话校验失败、持久化校验失败→隔离、绑定两次提交失败)在 head 上各自产生恰好一条带目标 sessionId、phase、reason、dispatchState、cleanupOutcome 的关联诊断(Standalone session creation failed.),原始 cause 以引用身份保留在内存错误链上;在 base(当前 main)上这些诊断完全不存在、cause 全部丢失——修改是 load-bearing 的。HTTP 公共契约在两个方向上都逐字节一致(500、Retry-After: 5、相同响应体),base 上派发前失败与来源未确认返回完全相同的响应体(正是本 PR 要解决的不可区分性)。SECRET_SENTINEL 标记未出现在 daemon warn、路由 warn、trace 参数或 HTTP 响应中。见下方 A/B 表格与截图 02-ab-cell-table.png。

测试非空虚:突变 M1(删除 catch 中的 recordCreationFailure)使 PR 新增诊断测试 8 个转红,且本验证探针 4/6 场景翻转为 base 表现(隔离路径由第二个调用点保持,属分层防护);突变 M2(invalid_ack→unknown)精确击中 2 个 bridge 分类测试。未突变对照全绿。见 03-mutation-matrix.png。

门禁:merged head 上 standalone-session-service.test.ts 122/122、error-response.test.ts 58/58、bridge.test.ts 全量 974/974;acpAgent.test.ts 783 通过 3 失败,但 base(main)上同样 3 个测试、同名失败(增量 +2 通过 / +0 失败)——属 main 上已存在的失败,与本 PR 无关。见 04-gates.png。

发现:无阻塞问题。注记:①main 上 3 个 acpAgent 测试在此 Linux 环境失败(既有问题,建议另行跟进);②PR 描述称 Linux 未测试,本轮在 Linux aarch64 上全部相关门禁通过;③main 已切换 pnpm(#11859 移除了 package-lock.json),merged 树需用 pnpm 安装——PR 分支本身基于旧 main,合入后无锁文件冲突。

未覆盖:真实 provider 推理与模型调用(按 PR 声明的范围外);Windows 平台;qwen serve 真实 daemon 端到端(本轮在真实 Express 路由 + 真实 service + 故障注入层面验证,与作者方案一致但独立实现);bridge 侧来源分类未做独立 A/B(以 PR 自身测试 + 突变为证)。

Central claim

When standalone session creation fails, the failure produces one correlated diagnostic carrying the target session ID, the failed phase, dispatch state and cleanup outcome, while the original cause survives in memory on the error chain — and public HTTP behavior (codes, messages, Retry-After, sourcePersisted) is unchanged and free of raw causes/secrets.

A/B: merged head vs current main (the PR diff is the only variable)

Identical observation harness (probe.test.ts, vitest, real StandaloneSessionService + real Express route via registerStandaloneSessionRoutes/sendBridgeError, fault injection at the documented dependency seams) run on both trees; 51 scripted comparator assertions (compare.mjs), 51 pass / 0 fail. Witness: 01-ab-comparator.png, cell summary 02-ab-cell-table.png.

Scenario base (main 615fcfcd) head (main+PR)
runtime acquisition fails no warn, no diagnostic 1 warn: phase=runtime, reason=runtime_changed, not_dispatched, not_needed; original error rethrown (identity)
spawn fails pre-dispatch (secret cause, real HTTP route) 500 + Retry-After: 5, cause lost, no warn identical 500 + Retry-After: 5 + byte-identical body; 1 warn phase=spawn_pre_dispatch, not_dispatched, rolled_back; creationDiagnostic on error; cause preserved; SECRET absent from warn/route-warn/trace/HTTP
source write unconfirmed (real HTTP route) same body as pre-dispatch cell (indistinguishable — the motivation) identical body as head's pre-dispatch cell, but diagnostic now distinct: phase=source_persistence, reason=source_not_confirmed, dispatched, rolled_back
direct child, parent missing error only public error unchanged (standalone_session_not_found, same message); diagnostic keeps target child ID with relatedSessionId=<parent> at phase=parent_validation
durable validation fails after persistence (secret cause) outcome_unknown, cause lost outcome_unknown, cause identity preserved, phase=durable_validation, dispatched, quarantined, no sibling kill (0 killSession calls), SECRET absent
binding commit fails twice (secret cause) outcome_unknown, cause lost outcome_unknown, cause identity preserved, phase=binding, dispatched, quarantined, SECRET absent

Reviewer Test Plan walk: all four bullets exercised — pre-dispatch + unconfirmed source write through the real route (rows 2–3); direct child against missing parent (row 4); cleanup failure / double binding-commit failure with cause chain and sibling survival (rows 5–6); secret sentinels checked in daemon warn, route warn, trace arguments and HTTP output (rows 2, 5, 6).

Test non-vacuity (mutation matrix)

Witness: 03-mutation-matrix.png.

Mutation Expected killer Result
M1: remove recordCreationFailure(attempt, error) from createInternal catch PR creation failure diagnostics tests 8/16 red; verifier probe flips 4/6 scenarios to base behavior (warn 1→0, diagnostic gone). Quarantine paths keep warning via the second call site (outcome path) — layered defense, both hunks load-bearing
M2: invalid_ack → unknown in persistSessionSource bridge classification tests exactly the 2 invalid_ack cases red ({persisted:'SECRET_ACK'}, null), 5 others green

Unmutated controls green (122/122, 58/58, 51/51 source-filtered), so the kills are attributable. Both mutations reverted afterward; working tree clean.

Gates (merged head)

Witness: 04-gates.png.

Suite Result
packages/cli standalone-session-service.test.ts 122/122 pass
packages/cli error-response.test.ts 58/58 pass
packages/acp-bridge bridge.test.ts (full, incl. real RPC timeout / closed-channel classification) 974/974 pass
packages/cli acpAgent.test.ts 783 pass, 3 fail

The 3 acpAgent.test.ts failures (accepts a plain positive decimal integer, gives each hosted session a record of its own…, status ext methods expose workspace and session snapshots without secrets) fail byte-identically on current main (781 pass, same 3 fail) — pre-existing environmental failures, delta +2 passing / +0 failing. Not attributable to this PR.

Findings

None blocking.

Notes for the record:

  1. Pre-existing main failures — the 3 acpAgent.test.ts cases above fail on unmodified main in this Linux environment; worth an independent look, unrelated to this PR.
  2. Platform coverage — the PR body lists Linux as untested; this round ran all targeted gates on Linux aarch64 (Node 24.14) green.
  3. Install mechanics after merge — main has retired package-lock.json (ci(pnpm): install with pnpm everywhere and retire package-lock.json #11859, pnpm-only); the merged tree installs with pnpm install --frozen-lockfile. No lockfile conflict; noted because maintainers verifying locally on the PR branch alone will still see the old npm layout.

Not covered

  • Live provider inference / model calls (declared out of scope by the PR).
  • Windows execution.
  • A real qwen serve daemon end-to-end; verification was at the real Express route + real service + fault-injection layer (independent re-implementation of the author's approach, not a replay of the author's harness).
  • Bridge-side source classification has no independent A/B from this round; evidence is the PR's own classification tests (full suite green) plus mutation M2.
  • Per-commit attribution (3 commits verified as an aggregate diff).

Methodology

Orange Pi host, Linux aarch64, Node 24.14.0, pnpm 11.24.0. Head tree = PR head e3a6643 merged onto main@615fcfcd (conflict-free; main had touched acpAgent.ts/error-response.ts since the merge-base, so the merged tree — not the raw branch — was verified). Base tree = main@615fcfcd; node_modules hardlink-copied from the head install (PR touches no dependency files), and readlink -f asserted that @qwen-code/qwen-code-core and @qwen-code/acp-bridge resolve into the base tree, which was then rebuilt (pnpm run build, exit 0). The probe (probe.test.ts, kept in the artifact dir and copied into both trees) records observations only; verdicts live in compare.mjs (51 assertions), the M1 flip script (6 assertions), the two mutation-kill checks, and the gate runs. Raw observation JSONs (probe-head.json, probe-base.json, probe-head-m1.json), the comparator, and build logs live alongside this report in the artifact dir.

Evidence

01-ab-comparator

02-ab-cell-table

03-mutation-matrix

04-gates

@callmeYe

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued for an available runner; follow the workflow run for progress. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes.

@callmeYe
callmeYe requested a review from wenshao September 23, 2026 08:44
@callmeYe
callmeYe enabled auto-merge September 23, 2026 08:46

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

Reviewed at e3a66436 (base 615fcfcd). Approving — no P0/P1 found.

Verified the change is diagnostics-only where it claims to be: the failure control flow (status codes, retryable flags, rollback/quarantine/close outcomes) is preserved branch-for-branch, and what changed is that each terminal error now carries its cause plus a CreationDiagnostic (phase / reason / dispatchState / cleanupOutcome) attached in recordCreationFailure. The parent-validation move into createInternal keeps the same conflict check and now attributes the phase correctly, and the child's own session id stays distinct from the parent-validation error's id.

The leak boundary is the part I checked hardest, and it is done right: the HTTP response payload is unchanged (err.message only), and for telemetry sendBridgeError records a sanitized copy when a creation diagnostic is present — cause and ad-hoc properties are stripped, with a test that pins SECRET_CAUSE/SECRET_PAYLOAD absence. The in-memory cause survives for server-side logs; emitDaemonLog/recordDaemonError/daemonLog.warn are all wrapped best-effort so diagnostics can never break creation. The swallowed-rejection fix in admitInitialPrompt (admissionFailure attached as cause) is a genuine diagnostics win.

Two nits, not worth a roundtrip: the phase = 'parent_validation' assignment is duplicated a few lines apart in createInternal, and client-caused 4xx (e.g. standalone_session_conflict) now also emit daemon-error telemetry — slightly noisy, but harmless. CI green at this head; no review threads.

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

PR 主旨:这是会话创建失败路径的纯诊断增强。给 standalone 会话创建的各阶段(runtime / parent_validation / durable_validation / directory_prepare / spawn_pre_dispatch / spawn_dispatched / source_persistence / model_selection / binding / initial_prompt)打上一次性 CreationAttempt 标注,失败时经 recordCreationFailure 产出一条固定文案的 telemetry(recordDaemonError + emitDaemonLog)与一条 daemonLog.warn,属性是枚举过的白名单标量;原始 cause 只在内存里挂在 Error 上供链式排查,绝不进 telemetry/日志/HTTP。同时给私有 source ack 补了可选 reason(recording_unavailable / write_not_confirmed),bridge 侧把未确认写入分类为 negative_ack/invalid_ack/rpc_timeout/transport_closed/rpc_rejected。公共契约(HTTP 状态码、body、Retry-After: 5、sourcePersisted)保持不变。

我按 head 静态复核,结论:无 Critical、无 Important。要点:

  • 规范化等价:parseRequiredSessionId(x).sessionId 与 normalizeSessionIdForLookup(x) 在合法 UUID 上走同一正则+小写;非法 child 仍在 createInternal 首行抛 invalid_request(与旧代码同位置同行为),自冲突判定语义不变,父会话仍是强制校验(commit「retain required parent validation」属实)。
  • recordCreationFailure 只在 createInternal 的 catch 里触发,transcript deletion / operation_failed 等路径不经过它,也不会带上 diagnostic。
  • CI 全绿,与你的实现一致:cause 不外泄在测试里对 SECRET_SENTINEL/SECRET_PARENT/SECRET_PROMPT/SECRET_CAUSE/SECRET_PAYLOAD 均有 JSON.stringify(...).not.toContain(...) 断言。

顺带复核了 ci-bot 的两条疑问,在最终 head 上都不成立:(1)「error-response.ts 把剥 cause 扩大到了每个 500」——其实 safeError 被 err.creationDiagnostic 严格门控,非创建类 500(transcript_deletion_failed / working_directory_recovery_failed)没有 diagnostic,仍原样上报;(2)「runtime / durable_validation / model_selection 三个阶段无测试断言」——本 head 的测试分别断言了这三个 phase。文档里「every unsuccessful source operation」对未走 persistSessionSource 的分支/默认会话略有夸大,属非阻塞的措辞问题,bot 已提,留作后续即可。

APPROVE。


What this PR does: A diagnostics-only enhancement to the standalone session-creation failure paths. A one-shot CreationAttempt is threaded through each phase (runtime / parent_validation / durable_validation / directory_prepare / spawn_pre_dispatch / spawn_dispatched / source_persistence / model_selection / binding / initial_prompt); on failure recordCreationFailure emits one fixed-wording telemetry record (recordDaemonError + emitDaemonLog) plus a daemonLog.warn line whose attributes are allowlisted scalars. The raw cause is kept only in memory, chained onto the Error for post-hoc debugging, and never serialized into telemetry/logs/HTTP. The private source ack gains an optional reason (recording_unavailable / write_not_confirmed), and the bridge classifies unconfirmed writes as negative_ack/invalid_ack/rpc_timeout/transport_closed/rpc_rejected. The public contract (HTTP status codes, body, Retry-After: 5, sourcePersisted) is unchanged.

Static re-review against the head; verdict: no Critical, no Important. Highlights:

  • Normalization equivalence: parseRequiredSessionId(x).sessionId and normalizeSessionIdForLookup(x) share the same regex + lowercasing for valid UUIDs; an invalid child still throws invalid_request on the first line of createInternal (same spot/behavior as before), so the self-conflict check keeps its meaning and parent validation remains required (the "retain required parent validation" commit holds).
  • recordCreationFailure fires only from createInternal's catch, so transcript-deletion / operation_failed paths never route through it and never carry a diagnostic.
  • CI is green, and cause non-leakage is asserted in tests via JSON.stringify(...).not.toContain(...) over the SECRET_* / SECRET_PAYLOAD sentinels.

I also re-checked the two questions raised by ci-bot; neither holds on the final head: (1) "the cause-stripping in error-response.ts now applies to every 500" — the safeError projection is strictly gated by err.creationDiagnostic, so non-creation 500s (transcript_deletion_failed / working_directory_recovery_failed) have no diagnostic and are recorded unchanged; (2) "phases runtime / durable_validation / model_selection are untested" — this head's tests assert all three phases. The design doc's "every unsuccessful source operation" slightly overclaims for the branched-session / default-session writes that don't go through persistSessionSource; non-blocking wording, already flagged by the bot, fine as a follow-up.

APPROVE.

@callmeYe
callmeYe added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 62f298c Sep 23, 2026
79 of 80 checks passed
wenshao added a commit to doudouOUC/qwen-code that referenced this pull request Sep 25, 2026
Several earlier merges of main into this branch resolved files with the
branch side and silently lost main's changes. They merged without
conflicts, so only tests and a line-level audit of each merge found them.

- acp-bridge: bring back main's split between bridge.ts, the session
  control plane and the channel harness (QwenLM#11916), which had been replaced
  by the pre-split monolith, and port the branch's paired execution
  engines, receipt validation, Managed Session store binding, prompt
  admission and teardown reporting into it. Main's runtime stop (QwenLM#12008),
  shared channel startup and source-persistence classification (QwenLM#12331)
  work again.
- core: restore the credential check that kept a parent API key from being
  sent to another endpoint, reasoning overrides on user turns, the
  fixed_policy bypass and permission-flow signal, the container execution
  guard for in-process agents, the sandbox guard for headless subagents,
  the omni_recall record subtype, tool span attributes and the auto-mode
  fallback message rule, and main's Managed Session record validation that
  the branch's writers never trip.
- cli: restore the ACP policy-artifact collection, omni media
  normalization and structured shell result diagnostics in Session, the
  concurrent SessionEnd wait, the default factory's child process registry
  and idle reclaim wiring, and the frozen Hosted Harness contract.
- web-shell: restore 50 capacity, footnote and daemon strings in both
  dictionaries, and keep the deep-linked Connections settings open when
  project features are unavailable.
- packaging: keep main's codeModeHost and bwrap sandbox worker entries in
  the standalone and npm package lists next to the managed runtime worker.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants