fix(cli): stop the TUI startup freeze when chat recording is disabled (#10293) - #10303
fix(cli): stop the TUI startup freeze when chat recording is disabled (#10293)#10303qwen-code-dev-bot wants to merge 5 commits into
Conversation
…#10293) waitForGoalRuntime moved the config.getGoalRuntimeReady() call out of its try/catch when the startup gate gained a timeout (#10128). With chat recording disabled (--no-chat-recording, general.chatRecording: false) that call THROWS GoalPersistenceUnavailableError synchronously instead of rejecting, so the throw escaped into the AppContainer init effect; the non-fatal unhandledRejection handler swallowed it and setConfigInitialized(true) never ran, freezing the interactive TUI on "Connecting to MCP servers..." forever. Every E2E suite that spawns the TUI with --no-chat-recording (the integration default) then polled out, red-lining the post-merge E2E lanes (run 33072022410; tracker #10272). Call getGoalRuntimeReady() inside the try again so the synchronous throw is treated exactly like the rejected promise, and pin both halves with unit tests: the persistence-unavailable throw settles the gate with and without the timeout, and any other synchronous throw still escapes.
Autofix E2E report — issue #10293 (E2E Tests run 33072022410)What failedIssue #10293 tracks the post-merge Root cause (reproduced, not guessed)The interactive TUI hangs forever at startup whenever chat recording is disabled (
Live evidence captured from the hung process while reproducing: the debug log contains Fix
Verification
Note: the full unsharded E2E suite was started as a diagnostic and stopped once the hang was reproduced; the targeted suites above cover every test class named in tracker #10272 plus the whole interactive directory. Tests quarantined by #10290 were not un-quarantined here — that is maintainer follow-up, not part of this fix. 中文说明Autofix E2E 报告 —— issue #10293(E2E Tests 运行 33072022410)失败现象Issue #10293 跟踪的是 根因(已复现,非猜测)只要禁用了聊天记录(
复现时从挂起进程中抓到的现场证据:debug 日志在 修复
验证
说明:完整未分片的 E2E 套件仅作为诊断启动,在复现挂起后即停止;上面的定向套件已覆盖 tracker #10272 点名的每一类测试以及整个 interactive 目录。#10290 隔离的测试未在本 PR 中解除隔离 —— 那是维护者的后续工作,不属于本修复范围。 🧠 Handled by Qwen Code · model/模型 |
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. Post-merge E2E run 33072022410 went red on every shard and #10293 was auto-filed; the hang reproduces deterministically whenever chat recording is disabled. I verified the mechanism from source independently of the PR's claims: Direction: aligned — this restores error handling that #10128 incidentally dropped from the startup gate; the timeout behavior itself stays untouched. Every interactive launch with Size: no core paths touched ( Approach: minimal — the call moves back inside the existing try/catch, which is exactly what the function's docstring already promises ("persistence unavailable is treated as settled"). The two new tests pin both directions: the synchronous throw settles the gate, any other synchronous throw still propagates. Nothing to cut. Risk: no elevated risk signals — neither changed file matches the revert-correlated paths. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 已观测到,非理论性问题。合并后 E2E 运行 33072022410 全部分片变红,#10293 被自动创建;只要禁用聊天记录,挂起即可确定性复现。我也独立于 PR 自述从源码核实了机制:禁用记录时 方向: 对齐 —— 本改动恢复了 #10128 无意间从启动门丢弃的错误处理;超时机制本身保持不变。当前每一次带 规模: 未触及核心路径( 方案: 最小化 —— 把调用移回既有的 try/catch 内部,这正是函数 docstring 早已承诺的语义("persistence unavailable 视为已收尾")。两个新测试钉住两个方向:同步抛出使启动门正常通过;其他同步抛出仍照常传播。无可删减。 风险: 无升级风险信号 —— 两个改动文件均未命中与 revert 相关的路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewNo blocking findings. I traced the failure path in the current source before reading the diff, and the change is exactly what it calls for:
Test evidence (the PR's own CI, read via API — no PR code executed)The Ubuntu unit suite is still running on the reviewed commit; everything that has completed is green and nothing has failed. Note that Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The author's E2E report in this thread (the author's own claim — not re-run here, since this review executes no PR code) adds a Linux before/after reproduction of the previously hanging Sandboxed verification would settle the one claim PR CI cannot: that an interactive launch with 中文说明代码审查无阻塞性问题。我在读 diff 之前先从当前源码追了失败链路,这份改动正是所需的最小修复:
测试证据(读取 PR 自身 CI —— 本审查未执行任何 PR 代码)被审提交上的 Ubuntu 单元测试套件仍在运行;目前已完成的检查全部为绿,无失败。注意 作者在楼下贴出的 E2E 报告(作者自述 —— 本审查未复跑,因为审查不执行任何 PR 代码)补充了此前挂起的 PR CI 无法覆盖的那个行为声明可用沙箱验证一锤定音:带 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — an exemplary regression fix: root cause proven against source, a four-line change restoring exactly the pre-#10128 semantics, and witness tests pinning both directions. Stepping back: this is what an autofix should look like. The problem was observed (every shard of E2E run 33072022410 red, #10293 auto-filed), the mechanism is verifiable from source independently of the PR's claims, and the fix is the smallest possible change — no drive-by edits, no new abstraction, timeout behavior untouched. My independent proposal for "the startup gate must swallow a synchronous persistence-unavailable throw" was identical to this diff, and the symmetric test pair is exactly the pin I would have asked for. Every user who disables chat recording currently hits a permanent startup freeze, so the value is unambiguous. In six months this reads as a clean, well-commented four-line repair — nothing to curse. CI is still running on the reviewed commit (Ubuntu suite in progress), so approval is deferred until CI lands green on 中文说明置信度:5/5 —— 教科书式的回归修复:根因经源码核实,四行改动精确恢复 #10128 之前的语义,见证测试钉住正反两个方向。 退一步看:这正是 autofix 该有的样子。问题是观测到的(E2E 运行 33072022410 全部分片变红、#10293 自动创建),机制可以独立于 PR 自述从源码验证,修复是最小改动 —— 无顺手改动、无新抽象、超时行为未动。我对"启动门必须吞掉同步的 persistence-unavailable 抛出"的独立方案与这份 diff 完全一致,对称的测试对也正是我会要求的钉法。每一位禁用聊天记录的用户目前都会遇到永久启动冻结,价值毫无歧义。六个月后再看,这就是一处干净、注释到位的四行修复。 被审提交上的 CI 仍在运行(Ubuntu 套件进行中),因此暂缓批准,待 CI 在该提交上全绿后由 finalize 任务执行提交绑定的批准。 — Qwen Code · qwen3.8-max Reviewed at |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
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.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Test Plan (not a blocker): 5 passed — this review observed 25345 passed; 8 passed — this review observed 25345 passed.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
Test Plan(非阻断):5 passed — this review observed 25345 passed; 8 passed — this review observed 25345 passed。
— qwen3.8-max via Qwen Code /review (v0.22.2)
| const getGoalRuntimeReady = vi.fn((): Promise<GoalRuntime> => { | ||
| throw failure; | ||
| }); | ||
|
|
||
| await expect(waitForGoalRuntime({ getGoalRuntimeReady })).rejects.toBe( | ||
| failure, | ||
| ); |
There was a problem hiding this comment.
[Suggestion] The new rethrow test pins error propagation only on the no-timeout path, but the production startup gate (AppContainer.tsx) always calls waitForGoalRuntime with a timeoutMs, i.e. through the Promise.race branch — and no test covers error propagation on that branch (the swallow test already asserts both). I mutation-verified the hole in an isolated scratch tree: changing the race entry to awaitReady().catch(() => 'timeout' as const) — which would turn a genuine Goal-state error (e.g. a malformed lifecycle record) at TUI startup into a silent false / "Goal features are degraded" outcome instead of surfacing it to the global error handler — leaves the whole file green (Tests 8 passed (8)), while adding the timeout-path assertion below kills the mutant (Tests 1 failed | 7 passed) and passes on the current code.
| const getGoalRuntimeReady = vi.fn((): Promise<GoalRuntime> => { | |
| throw failure; | |
| }); | |
| await expect(waitForGoalRuntime({ getGoalRuntimeReady })).rejects.toBe( | |
| failure, | |
| ); | |
| const getGoalRuntimeReady = vi.fn((): Promise<GoalRuntime> => { | |
| throw failure; | |
| }); | |
| await expect(waitForGoalRuntime({ getGoalRuntimeReady })).rejects.toBe( | |
| failure, | |
| ); | |
| await expect( | |
| waitForGoalRuntime({ getGoalRuntimeReady }, { timeoutMs: 100 }), | |
| ).rejects.toBe(failure); |
The added assertion is its own fix witness: reintroduce the swallowing mutant on the race branch and it is the line that goes red — confirm the red after adding it.
中文说明
新增的重抛(rethrow)测试只在无超时路径上钉住了错误传播,但生产环境的启动门(AppContainer.tsx)总是带 timeoutMs 调用 waitForGoalRuntime,也就是走 Promise.race 分支 —— 而该分支的错误传播没有任何测试覆盖(吞异常(swallow)测试已经同时断言了两个分支)。我在隔离的 scratch tree 中做了变异验证:把 race 的一项改成 awaitReady().catch(() => 'timeout' as const) —— 这会把 TUI 启动时真实的 Goal 状态错误(例如损坏的生命周期记录)变成静默的 false / "Goal features are degraded",而不是上抛给全局错误处理器 —— 整个测试文件仍然全绿(Tests 8 passed (8));而补上下方超时路径的断言即可杀死该变异体(Tests 1 failed | 7 passed),且在当前代码上通过。
新增的断言本身就是修复见证:在 race 分支重新引入吞异常的变异体,正是该断言会变红 —— 添加后请确认它确实变红。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🐑 Merge conflict with main detected — dispatched the autofix loop to resolve it. / 检测到与 main 的合并冲突,已触发 autofix 处理。 |
main independently landed the same fix for the synchronous getGoalRuntimeReady() throw via #10290. Consolidate the duplicate sync-throw tests into main's pair, keeping this PR's extra coverage (both race and no-timeout branches, per-call invocation count) and adding the timeout-path error-propagation assertion requested in review.
|
🤖 Addressed the latest review feedback (round 1/10). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/10 轮)。改动内容与我反驳保留之处如下: Round summaryAddressed the single inline finding and resolved the base conflict ( Feedback dispositions
Conflict resolution (merge of origin/main)
Net effect after the merge: this PR no longer duplicates main's fix. Its entire delta against Note on
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
— [unverified] tag when the loop ended — the verifier never ruled on them, and they are not confirmed.
Test Plan (not a blocker): 5 passed — this review observed 25511, 22087, 1794, 1667, 605, 4333, 639 passed; 8 passed — this review observed 25511, 22087, 1794, 1667, 605, 4333, 639 passed.
中文说明
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
— [unverified] 标记——验证者从未对它们作出裁决,它们不算已确认。
Test Plan(非阻断):5 passed — this review observed 25511, 22087, 1794, 1667, 605, 4333, 639 passed; 8 passed — this review observed 25511, 22087, 1794, 1667, 605, 4333, 639 passed。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), Post Coverage Comment (ubuntu-latest, 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), Post Coverage Comment (ubuntu-latest, 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
🔀 Base updated: red check(s) [review-pr] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [review-pr] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. 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): "agent 6b": run goal-runtime.test.ts under vitest — the worktree has no node_modules or built dist/ ( npm ci + npm run build would exceed the practical budget), so….
Not reviewed: reverse audit — stopped before round 1 by the review time budget.
Test Plan (not a blocker): 5 passed — this review observed 25512, 22089, 1794, 1690, 604, 4333, 639 passed; 8 passed — this review observed 25512, 22089, 1794, 1690, 604, 4333, 639 passed.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent 6b":run goal-runtime.test.ts under vitest — the worktree has no node_modules or built dist/ ( npm ci + npm run build would exceed the practical budget), so…。
未审查:反向审计——评审时间预算不足,未能开始第 1 轮。
Test Plan(非阻断):5 passed — this review observed 25512, 22089, 1794, 1690, 604, 4333, 639 passed; 8 passed — this review observed 25512, 22089, 1794, 1690, 604, 4333, 639 passed。
— qwen3.8-max via Qwen Code /review (v0.22.2)
| // The call itself must stay inside the try: with chat recording | ||
| // disabled (--no-chat-recording, settings) getGoalRuntimeReady() | ||
| // THROWS synchronously instead of rejecting, and an escaped throw |
There was a problem hiding this comment.
[Suggestion] The discipline this comment codifies is caller-side only, while the contract violation stays open at the producer: Config.getGoalRuntimeReady() (config.ts:8462-8468) is a non-async method whose first statement — this.getGoalRuntime() — throws GoalPersistenceUnavailableError synchronously when chat recording is disabled, so the Promise rejection channel never engages. That is despite the adjacent branch of the same method deliberately returning Promise.reject(new GoalPersistenceUnavailableError()) and the documented contract (goal-protocol.ts:223-224) saying the method rejects when persistence is unavailable; getGoalRuntimePrepared() leaks the same way.
This is not hypothetical: #10128 forgot exactly this discipline in this very function, the escaped throw froze the TUI on the init banner and red-lined the post-merge E2E lanes (#10293). Every future caller of either method must independently remember to keep the call itself inside a try/catch; the first one that doesn't — a new hook, command, or ACP path running with --no-chat-recording or general.chatRecording: false — turns the intended graceful degradation into an escaped throw and reproduces this incident. All 18 current call sites are wrapped, so nothing is broken today — the cost is the demonstrated repetition.
Consider normalising at the producer (here or in a follow-up, given core-config sensitivity) so the Promise contract holds for every caller and this comment's warning becomes moot:
getGoalRuntimeReady(): Promise<GoalRuntime> {
try {
const runtime = this.getGoalRuntime();
// ...existing readiness check...
return this.goalRuntimeReady.then(() => runtime);
} catch (error) {
return Promise.reject(error);
}
}Probe witness (isolated scratch tree, real new Config({ chatRecording: false })): the unmodified code prints PROBE ready: SYNC THROW GoalPersistenceUnavailableError (no Promise produced); with the try/catch normalisation applied, the same probe flips to PROBE ready: returned a Promise / promise REJECTED with GoalPersistenceUnavailableError, and all 10 existing Goal-related config tests still pass. This PR's caller-side change stays correct as defense in depth.
If the normalisation lands, the pinning test is the core-side counterpart of config.test.ts's 'does not expose volatile Goal state when chat recording is disabled': await expect(config.getGoalRuntimeReady()).rejects.toThrow(GoalPersistenceUnavailableError) with chatRecording: false — remove the normalisation and confirm that test goes red.
中文说明
这条注释所固化的约束只存在于调用方一侧,而契约违背在产生方仍然敞开:Config.getGoalRuntimeReady()(config.ts:8462-8468)是一个非 async 方法,其第一条语句 this.getGoalRuntime() 会在禁用聊天记录时同步抛出 GoalPersistenceUnavailableError,Promise 的 reject 通道根本没有机会生效——尽管同一方法的相邻分支刻意使用 return Promise.reject(new GoalPersistenceUnavailableError()),且文档契约(goal-protocol.ts:223-224)明确写明持久化不可用时该方法应当 reject;getGoalRuntimePrepared() 存在同样的泄漏。
这并非理论风险:#10128 正是在本函数中遗忘了这一约束,逃逸的抛出使 TUI 冻结在启动横幅上,并令合并后的 E2E 全线变红(#10293)。这两个方法的每一个未来调用方都必须各自记得把调用本身放进 try/catch;第一个忘记这样做的调用方——某个运行在 --no-chat-recording 或 general.chatRecording: false 下的新 hook、命令或 ACP 路径——会把预期的优雅降级变成逃逸的抛出,复现本次事故。当前全部 18 处调用点均已包裹,今天没有任何损坏——代价是这种已被证实会重演的重复劳动。
建议在生产方归一化(在本 PR 或后续跟进中,考虑到 core 配置的敏感性),使 Promise 契约对每个调用方都成立,这条注释的警告也就随之失去必要:
getGoalRuntimeReady(): Promise<GoalRuntime> {
try {
const runtime = this.getGoalRuntime();
// ...existing readiness check...
return this.goalRuntimeReady.then(() => runtime);
} catch (error) {
return Promise.reject(error);
}
}探针见证(隔离的 scratch tree,真实的 new Config({ chatRecording: false })):未修改的代码输出 PROBE ready: SYNC THROW GoalPersistenceUnavailableError (no Promise produced);应用 try/catch 归一化后,同一探针翻转为 PROBE ready: returned a Promise / promise REJECTED with GoalPersistenceUnavailableError,且现有 10 个 Goal 相关 config 测试全部通过。本 PR 调用方一侧的改动作为纵深防御仍然是正确的。
如果归一化落地,钉住它的测试是 config.test.ts 中 'does not expose volatile Goal state when chat recording is disabled' 的 core 侧对应版本:在 chatRecording: false 下 await expect(config.getGoalRuntimeReady()).rejects.toThrow(GoalPersistenceUnavailableError)——移除归一化后请确认该测试变红。
— qwen3.8-max via Qwen Code /review (v0.22.2)
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
|
Closing as superseded by #10290, which already landed the same goal-less startup fix on main. Post-merge E2E now passes the previously stalled interactive suites, so merging another duplicate implementation would add no fix. |
What this PR does
Restores the error handling that the interactive startup gate lost when it gained a timeout: the goal-runtime readiness lookup is now awaited inside the guard that treats "goal persistence is unavailable" as a normal, settled state, instead of letting it escape. Two unit tests pin the behavior — the unavailable-persistence case settles the gate whether or not the timeout is in play, and any other error still propagates.
Why it's needed
Since the startup gate was bounded (#10128), launching the interactive TUI with chat recording disabled —
--no-chat-recording(how the integration harness spawns every TUI) orgeneral.chatRecording: falsein settings — freezes the CLI forever onConnecting to MCP servers.... The readiness lookup throws synchronously when persistence is unavailable, the throw rejects the startup effect, the non-fatal unhandled-rejection handler keeps the process alive, and the initialized flag never flips. This red-lined every post-merge E2E lane (run 33072022410, tracked in #10272) and silently breaks the same startup path for real users who disable chat recording.Reviewer Test Plan
How to verify
Run the previously hanging E2E file and the new unit tests:
npm run build && npm run bundlenpx cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests interactive/external-context-mem0-write.test.ts— expect 5 passed in ~25s; before this fix every test hung at the startup banner until its poll timed out, withCRITICAL: Unhandled Promise Rejection! Reason: GoalPersistenceUnavailableErrorin the session debug log.cd packages/cli && npx vitest run src/ui/utils/goal-runtime.test.ts— expect 8 passed. Reverting the call back outside the try/catch makes the newtreats a synchronous persistence-unavailable throw as settledtest fail (mutation-verified).npx cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests interactive --exclude '**/interactive/cron-interactive.test.ts'— expect all files green.Evidence (Before & After)
Before: TUI stuck on
Connecting to MCP servers... (1/1) ⏳ 1 queuedfor minutes; debug log showsConfig initialization completedfollowed byCRITICAL: Unhandled Promise Rejection! Reason: GoalPersistenceUnavailableError: Goal persistence is unavailable for this session; every mem0-write test polls out (5 failed).After: the same suite passes 5/5 in ~25s; the full interactive directory passes (8 files passed | 1 platform skip).
Tested on
Environment (optional)
Linux self-hosted pool runner; bundled CLI with
QWEN_SANDBOX=falseintegration tests and package-local vitest for unit tests.Risk & Scope
Linked Issues
Fixes #10293
Related (no closing keyword): #10272 — this repairs the startup-freeze root cause its #10128 bisect identified; the quarantine/un-quarantine handling stays with the maintainers. #10128 — the change that incidentally moved the call out of the guard.
中文说明
本 PR 做了什么
恢复了交互式启动门在添加超时机制时丢失的错误处理:goal 运行时就绪状态的查询现在重新放在守卫内部 await —— 该守卫把 "goal 持久化不可用" 视为正常的已收尾状态,而不是任其向外逃逸。两个单元测试钉住该行为:持久化不可用的情况在有无超时两种情形下都让启动门正常通过;其他任何错误仍然照常传播。
为什么需要
自从启动门被加上超时限制(#10128)之后,在禁用聊天记录的情况下启动交互式 TUI ——
--no-chat-recording(集成测试框架启动每个 TUI 的方式)或 settings 中的general.chatRecording: false—— 会使 CLI 永远卡在Connecting to MCP servers...。当持久化不可用时,就绪状态查询会同步抛出异常,该异常使启动 effect reject,非致命的 unhandled-rejection 处理器让进程继续存活,而初始化完成标志永远不会翻转。这导致合并后的每一条 E2E 流水线变红(运行 33072022410,跟踪于 #10272),并且悄悄破坏了禁用聊天记录的真实用户的同一启动路径。评审者测试计划
如何验证
运行之前挂起的 E2E 文件和新增的单元测试:
npm run build && npm run bundlenpx cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests interactive/external-context-mem0-write.test.ts—— 预期 5 个测试在约 25 秒内通过;修复前每个测试都卡在启动横幅直到轮询超时,且会话 debug 日志中出现CRITICAL: Unhandled Promise Rejection! Reason: GoalPersistenceUnavailableError。cd packages/cli && npx vitest run src/ui/utils/goal-runtime.test.ts—— 预期 8 个通过。把调用退回 try/catch 之外会使新增的treats a synchronous persistence-unavailable throw as settled测试失败(已做变异验证)。npx cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests interactive --exclude '**/interactive/cron-interactive.test.ts'—— 预期全部文件为绿。证据(修复前后对比)
修复前:TUI 卡在
Connecting to MCP servers... (1/1) ⏳ 1 queued数分钟;debug 日志显示Config initialization completed之后出现CRITICAL: Unhandled Promise Rejection! Reason: GoalPersistenceUnavailableError: Goal persistence is unavailable for this session;mem0-write 的每个测试都轮询超时(5 个失败)。修复后:同一套件 5/5 通过、约 25 秒;整个 interactive 目录通过(8 个文件通过 | 1 个平台性跳过)。
测试环境
环境(可选)
Linux 自托管池 runner;打包后的 CLI 以
QWEN_SANDBOX=false运行集成测试,单元测试使用包内 vitest。风险与范围
关联 Issue
Fixes #10293
相关(不使用关闭关键字):#10272 —— 本 PR 修复了其 #10128 二分所定位的启动冻结根因;隔离/解除隔离的处理仍由维护者负责。#10128 —— 该改动无意间把调用移出了守卫。