Skip to content

fix(cli): prevent silent VP-mode crash by adding uncaughtException handler and error visibility - #8085

Closed
chiga0 wants to merge 267 commits into
QwenLM:mainfrom
chiga0:fix-vp-silent-crash
Closed

fix(cli): prevent silent VP-mode crash by adding uncaughtException handler and error visibility#8085
chiga0 wants to merge 267 commits into
QwenLM:mainfrom
chiga0:fix-vp-silent-crash

Conversation

@chiga0

@chiga0 chiga0 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a process.on('uncaughtException') handler and strengthens error visibility in VP (alternate-screen) mode. Related to #7971 #7972 #7779 #7781.

This PR does not claim to fix the crashes reported in those issues — it ensures that the next time a crash occurs, the error is captured in the debug log and visible on the terminal, so the actual root cause can be identified and fixed.

Why it's needed

Users report the CLI exiting silently during model streaming with VP mode enabled (ui.useTerminalBuffer: true) — no error message, no debug log entry, empty stderr.

Investigation of a real crash session (9b4af1de) confirmed:

  • The model stream stopped mid-thought (135 chars accumulated), then the process exited
  • No [ERROR], [FATAL], or exception entry in the debug log
  • No uncaughtException handler exists anywhere in packages/cli/src
  • PR fix(cli): surface unhandled rejections and render errors instead of swallowing them #7406's ErrorBoundary and unhandledRejection handler are present in 0.21.1 but only cover React render errors and promise rejections — synchronous exceptions bypass both

The silence mechanism: in VP mode, Node's default uncaught-exception stack trace goes to stderr → alternate screen buffer → discarded when teardown writes ?1049l. The user sees their shell prompt with no trace of what happened.

Changes

Core fix: uncaughtException handler

  • setupUncaughtExceptionHandler(sessionId) in gemini.tsx
  • Synchronous fs.appendFileSync to the debug log (async debugLogger.error() would be abandoned by process.exit)
  • Leaves the alternate screen (?1049l) before writing to stderr, so the error is visible on the main screen
  • Exits with code 1

ErrorBoundary (port of #7406 for internal sync)

  • ErrorBoundary.tsx with normalizeError, fallback prop, onError callback, reset recovery
  • Wraps AppWrapper with [FATAL_RENDER_ERROR] debug logging + 5s auto-exit
  • consumeLastRenderError() echoes the error to stderr after unmount leaves the alternate screen

VP teardown fixes

MarkdownDisplay defense

  • ThinkMessage/ThinkMessageContent wrapped with per-item ErrorBoundary — partial markdown during thought streaming degrades to plain text instead of crashing the VP tree

How to verify the fix works

After this PR, any crash will leave a trace:

# Search debug logs for the crash
grep -E "UNCAUGHT_EXCEPTION|FATAL_RENDER_ERROR|Unhandled Promise" ~/.qwen/debug/<session-id>.txt
Marker Error type Next step
[UNCAUGHT_EXCEPTION] Synchronous exception Stack trace points to the exact code
[FATAL_RENDER_ERROR] React render error Component stack points to the component
Unhandled Promise Rejection Async rejection Reason + stack point to the uncaught promise

No --debug flag needed — the debug log file is written by default.

Reviewer Test Plan

  1. cd packages/cli && bun run typecheck — passes
  2. npx vitest run src/ui/components/shared/ErrorBoundary.test.tsx — 7 tests pass
  3. npx vitest run src/ui/components/shared/VirtualizedList.test.tsx src/ui/components/shared/ScrollableList.test.tsx — 29 tests pass (no regression)

Risk & Scope

秦奇 and others added 30 commits March 30, 2026 20:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add beta version notice to Tips component
- Internationalize beta notice across all 6 locales (en/zh/ja/de/ru/pt)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
yiliang114 and others added 19 commits June 2, 2026 15:34
## 问题

NPM Publish CI run [QwenLM#174](https://code.alibaba-inc.com/alishu/qwen-code/ci/jobs?pipelineId=132250&pipelineRunId=44196709&createType=yaml) 在 `npm ci` 步骤失败:

```
npm error `npm ci` can only install packages when your package.json and
package-lock.json are in sync.
npm error Missing: @alife/dataworks-qwen-code-acp-bridge@0.17.0 from lock file
```

## 根因

上一个 MR [!129](https://code.alibaba-inc.com/alishu/qwen-code/codereview/27745054) 把 `packages/acp-bridge/package.json` 的 name 从 `@qwen-code/acp-bridge` 改成 `@alife/dataworks-qwen-code-acp-bridge`,但漏了重新生成 `package-lock.json`,导致 lockfile 里仍只有旧名字 `@qwen-code/acp-bridge` 的 link 条目,`npm ci` 严格校验直接报错。

正常 sync 流程里 `.aoneci/upstream-sync-merge.yml` Step 2.5 会自动跑 `npm install --package-lock-only --ignore-scripts`,但 !129 是手动改 manifest,不走 sync 流程,所以漏了这一步。

## 修复

```bash
npm install --package-lock-only --ignore-scripts
```

只在 `package-lock.json` 里追加了 `node_modules/@alife/dataworks-qwen-code-acp-bridge` 的 link 条目,并把 `packages/acp-bridge.name` 字段同步成新名(5 行 diff)。

## 验证

本地 `npm ci --ignore-scripts` exit 0,且产出 `node_modules/@alife/dataworks-qwen-code-acp-bridge` 软链。

## Test plan

- [ ] CI 上重新触发 NPM Publish pipeline,确认 `npm ci` 通过
- [ ] 后续 step(build / test / publish)正常进行
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/27752512
* fix(release): sync package-lock.json with renamed acp-bridge package

CI run #44196709 failed with:
  npm error `npm ci` can only install packages when your package.json and
  package-lock.json are in sync.
  npm error Missing: @alife/dataworks-qwen-code-acp-bridge@0.17.0 from lock file

Previous MR (!129) renamed packages/acp-bridge/package.json from
@qwen-code/acp-bridge to @alife/dataworks-qwen-code-acp-bridge but did not
regenerate package-lock.json, so CI's npm ci hard-fails.

This commit runs `npm install --package-lock-only --ignore-scripts` to add
the new name's link entry. Verified: `npm ci --ignore-scripts` now succeeds.
## 问题

NPM Publish 报错:

```
403 Forbidden - PUT https://registry.anpm.alibaba-inc.com/@qwen-code%2fsdk
"buc:jinjing.zzj" not authorized to modify @qwen-code/sdk
```

## 原因

`packages/sdk-typescript` 是 upstream 最近新增的包(`@qwen-code/sdk`),但 fork 的 `.fork/manifest.json` 没有这个映射,`rewrite-package-identity.js` 跳过了它,发布时尝试推到 `@qwen-code/sdk` 被 anpm 权限拦截。

## 修复

- `.fork/manifest.json`: 新增映射 `packages/sdk-typescript/package.json → @alife/dataworks-qwen-code-sdk`
- `packages/sdk-typescript/package.json`: 包名改为 `@alife/dataworks-qwen-code-sdk` + 加 `publishConfig.registry`(由 rewrite 脚本生成)

## 顺手检查的其他遗漏

跑了一遍 audit,所有非 private workspace 包的 `name` 字段都已正确改为 fork 名(manifest 已覆盖):
- ✅ acp-bridge, cli, core, sdk, web-templates, webui, channels/* 全部 OK
- ✅ vscode-ide-companion 保持 `qwen-code-vscode-ide-companion`(不在 @qwen-code scope,anpm 不拦)

## 一个独立的潜在问题(不在本 MR 范围)

15 个 workspace 内部 dep 引用还指向 `@qwen-code/*`,例如:

```json
// packages/cli/package.json
"@qwen-code/qwen-code-core": "file:../core",
"@qwen-code/acp-bridge": "file:../acp-bridge",
```

- 本地构建没问题(`file:` 协议靠 dep-key 走 symlink,和目标 package 的 `name` 字段无关)
- npm publish 本身不会失败
- 但下游用户从 anpm 安装 fork 包时,npm 会尝试解析这些 dep-key,去 anpm 找 `@qwen-code/qwen-code-core`,**找不到会失败**
- 需要单独写一个 dep-key rewrite 脚本,或者更新源码 import + dep key

历史上 fork 一直能发布说明这个问题之前要么没暴露,要么有别的兜底(比如 cli 单文件打包时清空了 dependencies)。建议后续单独排查。
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/27757910
* fix(publish): add sdk-typescript to identity rewrite mappings

@qwen-code/sdk is a new upstream package. Without an entry in
manifest.json.packageIdentity.mappings, rewrite-package-identity.js
leaves it under the @qwen-code scope and npm publish hits a 403
on the anpm registry (no permission to publish under @qwen-code).

Note: 15 cross-workspace dependency keys still reference @qwen-code/*
(e.g. cli depends on "@qwen-code/qwen-code-core": "file:../core").
Local builds work because file: links resolve by dep-key, not by the
target package's name field. Downstream installs from anpm will fail
to resolve these, but that's a separate problem from this publish
failure and needs a dedicated dep-key rewrite pass.

* fix: sync package-lock.json after sdk rename

* fix(publish): expand glob workspaces when bumping versions

The auto-version loop was iterating workspaces with literal path.join,
so "packages/*" produced a non-existent path and got skipped — only the
explicitly-listed channels got their version bumped. cli, core, sdk,
acp-bridge, web-templates, webui kept the raw 0.17.0 and collided
with previously-published versions on anpm (E403 "Can't modify
pre-existing version").

Add expandWorkspacePaths() that walks glob entries the way npm does,
then drive both the bump loop and the post-publish report off it.

* fix(test): bump anthropic claude-cli identity test timeout to 30s (CI slow)

* fix(publish): rename sdk to @alife/dataworks-qwen-code-sdk-typescript

@alife/dataworks-qwen-code-sdk was already taken on anpm (owner: gary.gq).
Switch to a fork-name that matches the source directory and is still free.

* fix(publish): mark vscode-ide-companion as private

This is a VSCode extension (publisher: qwenlm) — it ships through the
VSCode marketplace, not anpm. The name qwen-code-vscode-ide-companion
is already owned on anpm by another team (npm:usgupta), and publishing
there hits E403 every release. Setting private: true makes
publish-packages.js skip it without touching the existing anpm record.

* fix(publish): restore private:true on sdk-typescript

Both sdk-typescript and vscode-ide-companion were private:true on the
fork's main before the upstream sync. The sync replaced their
package.json with upstream's version which lacks the private field.

This is the root cause of the publish 403 errors — these packages
were never meant to be published to anpm.

* fix(ci): produce both tarball naming formats for backward compat

Downstream consumers depend on the old format:
  qwen-code-{version}-linux-amd64.tar.gz

Upstream switched to:
  qwen-code-standalone-{version}.tar.gz

Build both: build-standalone-ci.sh copies the tarball to the old name,
prepare-artifact.sh carries both into the artifact dir, and
upload-oss.sh uploads both to OSS so existing download URLs keep working.

* fix(test): extend waitFor timeout for paste mode test (CI slow)

* fix(ci): add --testTimeout=30000 to Aone CI test runs

Aone CI limits threads to 1-4 (vs config's 8-16) but kept the default
5s testTimeout. Timing-sensitive waitFor assertions fail intermittently
under the reduced concurrency. Align testTimeout with hookTimeout at 30s.

* fix(ci): raise vitest thread pool to 4-16 (machine has 32 cores)

* revert(ci): remove thread/timeout overrides, use vitest config defaults

* feat(ci): add release rollback pipeline

Manual-trigger pipeline for rolling back a bad release:
1. Repoints OSS channel metadata.json (beta/dataworks/latest) to an
   older version's metadata.json
2. Optionally runs npm deprecate on all non-private workspace packages
   for the bad version

Both steps support dry-run mode (default on).

Example usage: rollback beta channel to 0.15.11-dataworks.2 and
deprecate 0.17.0-beta.3.

* revert(test): remove unnecessary timeout overrides from test files

These timeout bumps were added to work around one-off CI flakes, not
real test-duration issues. Both tests pass reliably without them.

* refactor(publish): align with upstream — publish CLI from dist/ and channel-base only

Upstream release.yml publishes exactly two npm packages:
  1. CLI bundle from dist/ (after prepare:package)
  2. @qwen-code/channel-base from packages/channels/base

The fork was using `npm publish --workspaces` which publishes ALL
non-private workspace packages — causing 403 errors when sdk-typescript
and vscode-ide-companion (published via separate pipelines upstream)
got swept up.

Replace the blanket --workspaces publish with explicit per-package
publishes matching upstream. This eliminates the need for workarounds
(private:true, fork-renaming) on packages the fork doesn't publish.

Reverts: sdk-typescript/vscode-ide-companion package.json changes,
manifest.json sdk-typescript mapping, and related lockfile changes.

* fix: sync package-lock.json with sdk-typescript fork name

npm ci failed because package-lock.json still referenced the upstream
name @qwen-code/sdk while the workspace package.json (rewritten by
rewrite-package-identity.js) uses @alife/dataworks-qwen-code-sdk.
Run npm install --package-lock-only to bring them in sync.

* fix: restore sdk-typescript to upstream package name

sdk-typescript is not published by the fork's npm pipeline (it has
its own release pipeline upstream), so its package.json should keep
the upstream name @qwen-code/sdk — not the fork-rewritten name.
The 2026-06-03 sync replaced several fork customizations with upstream
versions. This commit restores them from main:

1. packages/core/src/mcp/constants.ts + oauth-provider.ts
   — getOAuthRedirectUri() with BFF_ENDPOINT/dsw_baseUrl proxy logic
   (patch 0004 exists but did not apply during sync)

2. .aoneci/release-rollback.yml
   — fork-only rollback pipeline (added to main after sync ran)

3. .aoneci/scripts/build-standalone-ci.sh, prepare-artifact.sh, upload-oss.sh
   — compat tarball format for downstream consumers

4. scripts/publish-packages.js
   — targeted publish (CLI bundle + channel-base only) instead of
     blanket --workspaces
Root cause: sync/upstream-20260603 was missing patches 0002-0011
(including DSW OAuth redirect patch 0004) due to three bugs:

1. Fork-only file restoration ran BEFORE apply.sh, causing patch
   0001's "new file" entry (ConfigInitDisplay.tsx) to hit "already
   exists in working directory" error.

2. apply.sh ran without --continue, so the first patch failure
   aborted all remaining patches.

3. Post-LLM-repair check only counted .rej files, but "already
   exists" errors don't produce .rej files — pipeline falsely
   declared success.

Fixes:
- Exclude patch-touched files from fork-only restoration
- Use --continue so individual failures don't block other patches
- Replace .rej-only check with full apply.sh --check validation
grep without -h prefixes each match with the source filename when
searching multiple files, so the sed extraction produced paths like
".fork/patches/0001.patch:packages/..." instead of "packages/...".
comm -23 couldn't match these against git ls-tree output, so no
files were actually excluded — 0001 and 0009 still hit "already
exists" errors.
Upstream reverted the swarm tool (QwenLM#3468) and removed SWARM from
ToolNames/ToolDisplayNames. The fork still keeps swarm.ts as a
fork-only file, so the constants must remain for compilation.
workspaceAgents, workspaceMemory, and AuthDialog tests had fork-
specific adaptations on main that were overwritten by upstream
content during sync. Restore from main.
CI runs as root where chmod 0o500 cannot prevent deletion, causing
the test to fail with ENOENT in the finally block. Skip the test
on root, and guard chmodSync with existsSync.
- Add --testTimeout=30000 to core test run in npm-publish pipeline
  (Config sessionEnvClaimed tests use vi.isolateModules which is slow
  in CI, hitting the default 5s timeout)
- Add --testTimeout=30000 to cli test run alongside existing hookTimeout
- Restore skill-activation.test.ts fork adaptations from main
DingTalk interactive card streaming is no longer in use. All content
in this patch is obsolete:
- cancelCurrentPrompt() in ChannelBase: no callers remain
- Card streaming in DingtalkAdapter: feature deprecated
- markdown.ts convertTables removal: upstream version is correct

The sync branch now follows upstream's simplified DingTalk adapter
(markdown-only, no card).
Title: chore: upstream sync 2026-06-03 (244 commits)

## Upstream Sync 2026-06-03

### 概要
- 合并了 **244** 个 upstream 提交
- 源: QwenLM/qwen-code main
- Sync 分支: `sync/upstream-20260603`

### 验证
- ⏳ 验证将在 MR 创建后异步执行

### 🎯 高风险文件(fork patch 触动过且本次 sync 也修改)

**reviewer 重点 review 这 89 个文件**(与 `.fork/patches.md` 中 fork 类 commit 触动过的文件交集):

- `.gitignore`
- `docs-site/package.json`
- `docs/users/configuration/settings.md`
- `docs/users/reference/keyboard-shortcuts.md`
- `esbuild.config.js`
- `integration-tests/interactive/context-compress-interactive.test.ts`
- `package-lock.json`
- `package.json`
- `packages/acp-bridge/package.json`
- `packages/channels/base/package.json`
- `packages/channels/base/src/ChannelBase.ts`
- `packages/channels/dingtalk/package.json`
- `packages/channels/dingtalk/src/DingtalkAdapter.ts`
- `packages/channels/dingtalk/src/markdown.ts`
- `packages/channels/feishu/package.json`
- `packages/channels/plugin-example/package.json`
- `packages/channels/telegram/package.json`
- `packages/channels/weixin/package.json`
- `packages/cli/package.json`
- `packages/cli/src/acp-integration/session/Session.test.ts`
- `packages/cli/src/acp-integration/session/Session.ts`
- `packages/cli/src/commands/channel/channel-registry.ts`
- `packages/cli/src/config/keyBindings.ts`
- `packages/cli/src/config/settingsSchema.ts`
- `packages/cli/src/gemini.test.tsx`
- `packages/cli/src/gemini.tsx`
- `packages/cli/src/i18n/locales/de.js`
- `packages/cli/src/i18n/locales/en.js`
- `packages/cli/src/i18n/locales/fr.js`
- `packages/cli/src/i18n/locales/ja.js`
- `packages/cli/src/i18n/locales/pt.js`
- `packages/cli/src/i18n/locales/ru.js`
- `packages/cli/src/i18n/locales/zh.js`
- `packages/cli/src/serve/workspaceAgents.test.ts`
- `packages/cli/src/serve/workspaceMemory.test.ts`
- `packages/cli/src/services/tips/tipRegistry.ts`
- `packages/cli/src/ui/AppContainer.tsx`
- `packages/cli/src/ui/auth/AuthDialog.test.tsx`
- `packages/cli/src/ui/components/Footer.test.tsx`
- `packages/cli/src/ui/components/Footer.tsx`
- `packages/cli/src/ui/components/InputPrompt.test.tsx`
- `packages/cli/src/ui/components/InputPrompt.tsx`
- `packages/cli/src/ui/components/MainContent.tsx`
- `packages/cli/src/ui/components/ModelDialog.tsx`
- `packages/cli/src/ui/components/Tips.test.ts`
- `packages/cli/src/ui/components/Tips.tsx`
- `packages/cli/src/ui/components/agent-view/AgentComposer.tsx`
- `packages/cli/src/ui/components/hooks/HooksManagementDialog.test.tsx`
- `packages/cli/src/ui/components/hooks/HooksManagementDialog.tsx`
- `packages/cli/src/ui/components/hooks/constants.ts`

> 共 89 个文件,仅展示前 50 个;完整清单见 CI artifact 或重跑流水线。

### Review 要点
请查看每日 Upstream Sync 分析 CI 了解完整差异状态。
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/27796570
## 问题

2026-06-03 的 upstream sync 静默丢失了多个 fork patches(0002, 0003, 0005, 0007, 0010, 0011)。

根因:
1. CI sync 流程的 reuse 逻辑只检查 `upstream/main` 是否是 sync 分支的 ancestor,不验证 fork patches 是否已 apply。前一次失败运行 push 了 patch 未打上的分支后,后续重试直接复用了这个不完整的分支。
2. sync 分支合入 main 后没有任何 CI 步骤验证 fork patch 内容完整性。

## 修复

### Commit 1: 恢复丢失的 patches

重新 apply 了 6 个丢失的 fork patches:
- **0002** branding-tips — DataWorks usage tips
- **0003** i18n-dataworks — zh/en DSW 环境翻译
- **0005** osc8-internal — `OPENCODE_TERMINAL` / `BFF_TOKEN` / `TERM=xterm-256color` 检测,修复内部 web terminal 中长链接换行后点击断开的问题
- **0007** feishu-channel — 飞书频道注册
- **0010** build-single-bundle — 单包构建配置
- **0011** test-fork-adaptations — doctorChecks Node v22 版本守卫

### Commit 2: CI 防护

- **新增 `.fork/verify-patches.sh`** — 直接检查每个 patch 的 added content 是否存在于工作树中(与 `apply.sh --check` 互补:后者检查 patch 能否被 apply,即内容不在;本脚本检查内容是否已在)
- **修复 reuse 逻辑** — 复用 sync 分支前先跑 `verify-patches.sh`,不完整则 fall through 到完整重建流程
- **新增 post-sync gate** — 提交 sync 结果后、创建 MR 前验证 patch 内容,失败则标记冲突并发送钉钉告警

## 测试

- `bash .fork/verify-patches.sh --verbose` 在当前分支上全部 PASS(9/9, 100%)
- 模拟回退 0005 patch 后 verify-patches.sh 正确报告 FAIL(37%)
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/27900189
* fix(fork): restore fork patches lost during upstream sync 2026-06-03

Patches 0002, 0003, 0005, 0007, 0010, 0011 were dropped when the
sync/upstream-20260603 branch was built — apply.sh either failed or
its results were discarded by the reuse-sync-branch shortcut.

Restored:
- 0002 branding-tips: DataWorks usage tips in tipRegistry
- 0003 i18n-dataworks: zh/en locale strings for DSW environment
- 0005 osc8-internal: OPENCODE_TERMINAL / BFF_TOKEN / TERM=xterm
  detection in supportsHyperlinks() — fixes broken link clicks in
  internal web terminal
- 0007 feishu-channel: Feishu (Lark) channel registration
- 0010 build-single-bundle: single-bundle esbuild config
- 0011 test-fork-adaptations: Node v22 guard in doctorChecks test

* fix(ci): add fork patch content verification to upstream sync pipeline

The 2026-06-03 upstream sync silently lost fork patches because:
1. The reuse-sync-branch shortcut skipped patch verification — if a
   previous run pushed a branch with failed patches, subsequent retries
   reused it without checking.
2. No post-sync step verified that patch content survived in the
   final tree.

Changes:
- Add .fork/verify-patches.sh: checks whether each patch's added
  content is present in the working tree (complements apply.sh --check
  which tests the opposite — whether patches CAN be applied).
- Fix reuse logic: run verify-patches.sh before reusing a sync branch;
  fall through to full rebuild if patches are incomplete.
- Add post-sync gate: run verify-patches.sh after committing the sync
  result; block MR creation and alert via DingTalk if patches are lost.

* fix(fork): restore 0009 patch — remove WebSearch:None mapping

Patch 0009-claude-websearch-compat removed the `WebSearch: 'None'`
line from claude-converter.ts to enable WebSearch in Claude compat
mode. This deletion was missed in the initial restore because the
patch file failed to apply cleanly (already-exists conflict on the
test file), and the deletion in claude-converter.ts was overlooked.

* fix(fork): align claude-converter.ts blank line with patch 0009

Keep the blank line where `WebSearch: 'None'` was removed, matching
the exact output of 0009-claude-websearch-compat.patch.
## 背景

6/10 定时任务 Upstream Sync 失败(pipeline run 45917108),钉钉告警列出 9 个 patch 失败,实际只有 1 个真实冲突。

## 问题与修复

**1. patch 0001 上下文过期(真实冲突)**

上游 6d083fa (PR QwenLM#4741) 把 AppHeader.test.tsx 的断言从 'gemini-pro' 改为 'Gemini Pro',导致 0001-branding-header.patch 第一个 hunk 的上下文行失配被 reject。

修复:刷新 patch 上下文行。已在 upstream/main (debd874) 的 worktree 上验证 9 个 patch 全部干净 apply(Applied: 9, Failed: 0)。

**2. LLM 修复后的成功判定逻辑 bug**

流水线在 LLM 修复后用 apply.sh --check(正向 dry-run)判定是否成功,但此时多数 patch 已打上,正向 check 必然全部 FAIL——即使 LLM 修好了也会被判失败,且钉钉告警因此把 9 个 patch 全列为冲突(误报)。

修复:apply.sh 新增 --check-applied 模式(git apply --reverse --check 验证已应用状态),流水线的修复后判定和失败列表改用该模式。已实测三种场景(未应用/全应用/部分应用)行为正确,YAML lint 通过。

## 备注

本次 401(CI_QWEN_API_KEY 过期)已另行更换。
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/27956413
* fix(fork): refresh patch 0001 context for upstream model display name change

Upstream 6d083fa (PR QwenLM#4741) changed the AppHeader test assertion from
'gemini-pro' to 'Gemini Pro', breaking the context line of patch 0001's
first hunk. Verified all 9 patches apply cleanly on upstream/main debd874.

* fix(ci): use reverse-apply check to verify patches after LLM repair

The post-repair success gate ran 'apply.sh --check' (forward dry-run) on a
tree where patches were already applied, so every patch reported FAIL and
the LLM repair path could never be marked successful — even with a valid
API key. The same forward check also produced the misleading DingTalk
alert listing all 9 patches as conflicting when only one actually was.

Add 'apply.sh --check-applied' (git apply --reverse --check) to verify the
applied state, and use it for both the post-repair gate and the failed-
patch list.
## What this PR does

Refreshes the fork upstream-sync patch stack so it applies cleanly on the latest upstream main while preserving the DataWorks package-name, Claude WebSearch, and single-bundle build customizations.

## Why it's needed

The scheduled upstream sync failed because three fork patches no longer matched upstream's current channel registry, Claude tool mapping, and build configuration shapes. Refreshing the patch contexts lets the sync pipeline replay the fork customizations without manual reject-file repair.

## Reviewer Test Plan

### How to verify

Run the upstream sync patch replay against the latest upstream main and confirm all patches report OK or APPLIED with zero failed patches. Confirm the final patch content verification reports PASS for all patches.

### Evidence (Before & After)

Before: CI run 47649001 failed during patch replay with 0007-feishu-channel.patch, 0009-claude-websearch-compat.patch, and 0010-build-single-bundle.patch not fully applied.

After: On upstream/main 3d6b6f7, .fork/apply.sh --check reported Check complete: 0 failed; full replay reported Applied: 9 Failed: 0; .fork/apply.sh --check-applied reported Check complete: 0 failed; .fork/verify-patches.sh --verbose reported PASS: 9 | WARN: 0 | FAIL: 0 | SKIP: 0.

### Tested on

|     OS     | Status |
| :--------: | :----: |
|  🍏 macOS  | ✅ tested |
| 🪟 Windows | N/A |
|  🐧 Linux  | N/A |

### Environment (optional)

Local git worktree using upstream/main 3d6b6f7 and the current fork patch metadata.

## Risk & Scope

- Main risk or tradeoff: This changes only patch metadata; the next scheduled sync still needs CI confirmation in the Aone environment.
- Not validated / out of scope: Full npm build and full test suite were not run.
- Breaking changes / migration notes: None expected.

## Linked Issues

N/A

<details>
<summary>中文说明</summary>

## 这个 PR 做了什么

刷新 fork 的 upstream-sync patch 栈,使其可以干净应用到最新 upstream main,同时保留 DataWorks 包名、Claude WebSearch 兼容和单文件 bundle 构建定制。

## 为什么需要

定时 upstream sync 失败是因为三个 fork patch 不再匹配 upstream 当前的 channel registry、Claude tool mapping 和构建配置结构。刷新 patch 上下文后,同步流水线可以重新回放 fork 定制,不再需要人工处理 reject 文件。

## Reviewer Test Plan

### How to verify

基于最新 upstream main 运行 upstream sync patch replay,确认所有 patch 都是 OK 或 APPLIED,且 failed 为 0。确认最终 patch 内容验证全部 PASS。

### Evidence (Before & After)

Before: CI run 47649001 在 patch replay 阶段失败,0007-feishu-channel.patch、0009-claude-websearch-compat.patch 和 0010-build-single-bundle.patch 没有完全应用成功。

After: 在 upstream/main 3d6b6f7 上,.fork/apply.sh --check 输出 Check complete: 0 failed;完整 replay 输出 Applied: 9 Failed: 0;.fork/apply.sh --check-applied 输出 Check complete: 0 failed;.fork/verify-patches.sh --verbose 输出 PASS: 9 | WARN: 0 | FAIL: 0 | SKIP: 0。

### Tested on

|     OS     | Status |
| :--------: | :----: |
|  🍏 macOS  | ✅ tested |
| 🪟 Windows | N/A |
|  🐧 Linux  | N/A |

### Environment (optional)

使用本地 git worktree,基于 upstream/main 3d6b6f7 和当前 fork patch metadata。

## Risk & Scope

- Main risk or tradeoff: 本次只修改 patch metadata;下一次定时同步仍需要在 Aone CI 环境中确认。
- Not validated / out of scope: 没有运行完整 npm build 和完整测试套件。
- Breaking changes / migration notes: 预期无。

## Linked Issues

N/A

</details>
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/28102374
* fix(ci): refresh upstream sync patches
Link: https://code.alibaba-inc.com/alishu/qwen-code/codereview/28129648
* fix(mcp): 统一 OAuth redirect_uri 拼接并支持 ACS_SANDBOX 代理段

- registerClient/exchangeCodeForToken 改用 getOAuthRedirectUri(),与授权阶段
  统一,避免 DSW/BFF 代理环境下 token 交换因 redirect_uri_mismatch 失败
- authenticate() 注册前一次性冻结 config.redirectUri,防止流程中途 env 变化
  导致注册/授权/换 token 三处 redirect_uri 发散
- getOAuthRedirectUri(): DA_RUNTIME_TYPE=ACS_SANDBOX 时代理段用 bxkxuth(新版
  Data Agent 实例),否则保持 kxuth;BFF_ENDPOINT 去尾斜杠
- 新增 constants.test.ts 及 oauth-provider.test.ts 集成测试,覆盖各分支与
  三处 redirect_uri 一致性
- 重生成 patch 0004 纳入测试文件,manifest 补充 paths 与 tests 条目
…ndler and error visibility

VP (alternate-screen) mode swallows all error output: uncaught exceptions
write their stack trace to stderr which lands on the alternate screen
buffer, then gets discarded when teardown switches back to the primary
buffer. The user sees a silent exit with no error message and nothing in
the debug log.

Root cause: no `uncaughtException` handler existed anywhere in the CLI.
PR QwenLM#7406's ErrorBoundary and unhandledRejection handler only cover React
render errors and promise rejections — synchronous exceptions bypass both.

Changes:
- Add `setupUncaughtExceptionHandler()` with sync debug-log write,
  alternate-screen exit before stderr output, and clean process.exit(1)
- Port QwenLM#7406's ErrorBoundary (normalizeError, fallback, onError, reset)
  for the internal repo which hasn't synced upstream yet
- Wrap AppWrapper with ErrorBoundary + [FATAL_RENDER_ERROR] logging +
  5s auto-exit (matching QwenLM#7406's startInteractiveUI.tsx pattern)
- Echo last render error to stderr after unmount leaves the alternate
  screen (consumeLastRenderError)
- Fix Kitty protocol teardown ordering: disableKittyProtocol() now runs
  after instance.unmount() so the pop lands on the correct screen buffer
- Remove SIGTERM/SIGINT handlers from kittyProtocolDetector.ts that
  raced with the main signal handlers (QwenLM#7779)
- Add SIGHUP handler alongside SIGTERM/SIGINT (QwenLM#7781)
- Guard ThinkMessage/ThinkMessageContent with per-item ErrorBoundary so
  partial markdown during thought streaming degrades to plain text
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

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

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

@chiga0

chiga0 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Closing — branch was based on internal main, not upstream. Re-opening with a clean branch.

@chiga0 chiga0 closed this Jul 30, 2026
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, and for the detailed investigation into the silent VP-mode crashes — the crash session analysis (9b4af1de) and the stderr-to-alternate-screen explanation are genuinely useful.

Template: mostly there, but missing the ## Linked Issues section and the Chinese <details> translation. The issues are referenced inline (#7971 #7972 #7779 #7781), so this is minor.

Problem: real and observed. Users are hitting silent exits during model streaming in VP mode with no error trace — the linked issues and the debug log analysis confirm this. The silence mechanism (stderr → alternate screen → discarded on ?1049l) is well explained.

Direction: aligned. Error visibility for silent crashes is squarely within scope.

Approach — this is the big one. This PR carries 250 commits and 1,149 changed files (+237K / −26K lines) from what appears to be an internal DataWorks fork that has diverged substantially from upstream main. The actual fix described in the PR body touches about four files (gemini.tsx, ErrorBoundary.tsx, ErrorBoundary.test.tsx, kittyProtocolDetector.ts), but the diff also includes fork-specific infrastructure (.aoneci/, .fork/ patches, branding, internal CI scripts, DataWorks features, channel packages, and extensive changes across packages/core and packages/cli).

More importantly, upstream main already has most of what this PR adds:

  • ErrorBoundary.tsx exists upstream (with sanitizeTerminalText, used in TranscriptView and startInteractiveUI.tsx)
  • An uncaughtException handler exists in cli.ts (with PTY-race suppression)
  • startInteractiveUI.tsx already wraps AppWrapper with ErrorBoundary + [FATAL_RENDER_ERROR] logging + 5s auto-exit — the exact pattern the PR describes
  • Kitty teardown (disableKittyProtocol()) already runs after instance.unmount() in the upstream cleanup chain

The fork's gemini.tsx inlines the interactive UI setup, while upstream delegates to startInteractiveUI.tsx — so the PR's changes to gemini.tsx don't map onto upstream's architecture.

The genuinely new ideas that upstream doesn't have yet:

  1. Sync debug-log write inside the uncaughtException handler (upstream's cli.ts handler only writes to stderr)
  2. Leaving the alternate screen (?1049l) before writing the error to stderr, so it's visible on the main screen
  3. consumeLastRenderError() to echo render errors to stderr after unmount
  4. SIGHUP handler alongside SIGTERM/SIGINT
  5. Removing the SIGTERM/SIGINT handlers from kittyProtocolDetector.ts that race with the main signal handlers (these still exist upstream)

These are worth upstreaming — but as a focused PR rebased on current main, touching cli.ts and startInteractiveUI.tsx rather than the fork's gemini.tsx.

Size: 253 production core-module files touched. The vast majority is fork divergence, not the fix. The 1000+ production-line advisory applies many times over.

Risk: 22 high-risk paths matched (geminiChat, openaiContentGenerator, shellExecutionService, mcp-client, LspServerManager, relaunch, acp-integration, shell.ts) — all from the fork divergence, not the fix itself. These paths have the strongest correlation with post-merge reverts in this repo.

CI: no build/test/lint checks have run on this commit — only bot orchestration. Without CI, there's no evidence the diff compiles against upstream.

I'd suggest closing this PR and opening a new one with just the five incremental improvements listed above, rebased on current main. The underlying investigation is solid and the ideas are good — they just need to land in the right files on the right base.

Flagging for @wenshao's attention given the scope and the core-module breadth.

中文说明

感谢这个 PR,也感谢对 VP 模式静默崩溃的详细调查——crash session 分析(9b4af1de)和 stderr 写入 alternate screen 被丢弃的解释非常有价值。

模板: 基本完整,缺少 ## Linked Issues 独立章节和中文 <details> 翻译。Issues 已在正文中引用(#7971 #7972 #7779 #7781),问题不大。

问题: 真实且已观测到。用户在 VP 模式下模型流式输出时遇到静默退出,无任何错误痕迹——关联的 issues 和 debug log 分析证实了这一点。

方向: 对齐。崩溃错误可见性完全在项目范围内。

方案——这是核心问题。 这个 PR 包含 250 个 commit、1149 个变更文件(+237K / −26K 行),来自一个与上游 main 大幅分叉的内部 DataWorks fork。PR 描述中的实际修复只涉及约四个文件,但 diff 还包括 fork 专属基础设施(.aoneci/.fork/ 补丁、品牌、内部 CI 脚本、DataWorks 功能、channel 包等)。

更关键的是,上游 main 已经有了这个 PR 添加的大部分内容:

  • ErrorBoundary.tsx 已存在上游
  • cli.ts 中已有 uncaughtException handler
  • startInteractiveUI.tsx 已用 ErrorBoundary 包裹 AppWrapper + [FATAL_RENDER_ERROR] 日志 + 5 秒自动退出
  • Kitty 协议 teardown 已在上游 cleanup chain 中于 instance.unmount() 之后执行

fork 的 gemini.tsx 内联了交互式 UI 设置,而上游委托给 startInteractiveUI.tsx——所以 PR 对 gemini.tsx 的修改无法映射到上游架构。

真正新的、上游还没有的改进:

  1. uncaughtException handler 中同步写 debug log
  2. 写 stderr 前先退出 alternate screen(?1049l
  3. consumeLastRenderError() 在 unmount 后回显渲染错误
  4. SIGHUP handler
  5. 移除 kittyProtocolDetector.ts 中与主信号处理器竞争的 SIGTERM/SIGINT handler

这些值得合入上游——但应该作为一个聚焦的 PR,基于当前 main rebase,修改 cli.tsstartInteractiveUI.tsx 而非 fork 的 gemini.tsx

规模: 触及 253 个核心模块生产文件,绝大部分是 fork 分叉。

风险: 匹配 22 条高风险路径(均来自 fork 分叉)。

CI: 无构建/测试/lint 检查运行。

建议关闭此 PR,基于当前 main 重新提交一个只包含上述五项增量改进的聚焦 PR。

Qwen Code · qwen3.8-max-preview

Reviewed at 476dfd06c1165f4ef9cf224db5bb85dd72b1faeb · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review

Independent proposal: given the problem (silent VP-mode exits, no error trace), I'd make five small changes to upstream's existing code: (1) add a sync fs.appendFileSync debug-log write to the existing uncaughtException handler in cli.ts, (2) have that handler emit ?1049l + ?25h before writing to stderr so the error lands on the main screen, (3) add consumeLastRenderError() to the existing ErrorBoundary.tsx and echo it in startInteractiveUI.tsx's cleanup chain, (4) add SIGHUP to the signal handlers, (5) remove the SIGTERM/SIGINT listeners from kittyProtocolDetector.ts. That's roughly five files, ~100 lines.

Comparison with the PR: the PR's fix logic matches this proposal closely — the setupUncaughtExceptionHandler, the alternate-screen exit before stderr, the consumeLastRenderError pattern, the SIGHUP addition, and the kitty signal-handler removal are all the right ideas. But they're implemented in the fork's gemini.tsx (which inlines the interactive UI setup) rather than upstream's cli.ts + startInteractiveUI.tsx, and they're buried in 1,149 files of fork divergence.

Findings on the fix code itself:

  • ErrorBoundary.tsx would regress upstream's version. The PR's file is a near-copy of upstream's existing ErrorBoundary.tsx but drops the sanitizeTerminalText() call on error.message in the default fallback. Upstream added that to prevent terminal escape injection through crafted error messages. Merging this file as-is would silently remove that protection. The genuinely new part — consumeLastRenderError() + the module-level lastRenderError store — is clean and could be added to upstream's existing file in a few lines.

  • setupUncaughtExceptionHandler in gemini.tsx is well-structured: sync write (correct — async would be abandoned by process.exit), alternate-screen exit before stderr, best-effort try/catch around both. But upstream's uncaughtException handler lives in cli.ts and also handles PTY-race suppression (isExpectedPtyRaceError). A focused PR would extend the existing handler rather than adding a parallel one in a different file.

  • kittyProtocolDetector.ts change is the most directly upstream-applicable diff: removing the SIGTERM/SIGINT handlers that race with installInteractiveSignalHandlers(). The process.on('exit', disableProtocol) fallback is correctly kept. This one could land as-is.

  • ErrorBoundary.test.tsx — 7 well-structured tests (healthy render, default/custom fallback, onError, reset recovery, non-Error normalization, consumeLastRenderError). These test the PR's ErrorBoundary variant; a focused PR would add the consumeLastRenderError test to upstream's existing test suite.

  • The debugLogger.error(errorMessage) addition to setupUnhandledRejectionHandler is a good catch — ensures the debug log always records the rejection even when no UI listener is active. This is a one-line change that applies cleanly to upstream.

The remaining ~1,145 files are fork divergence (DataWorks features, .aoneci/ CI, .fork/ patches, branding, channel packages, core refactors) that I have not reviewed in detail — they are unrelated to the stated fix and would need separate, focused PRs if any of them are intended for upstream.

Testing

Check Conclusion
precheck-pr / precheck ✅ success
label ✅ success
authorize ✅ success
triage 🔄 in progress
review-pr ⏭️ skipped / cancelled
verify ⏭️ skipped
tmux-testing ⏭️ skipped

No build, lint, typecheck, or unit-test CI has run on this commit — only bot orchestration checks. The precheck-pr gate passed, but the actual test suite has not been triggered (fork PRs may require workflow run approval). There is no CI evidence that this diff compiles against upstream main.

Sandboxed verification would settle the fix's behavioural claim: @qwen-code /verify — that the uncaughtException handler actually writes to the debug log and leaves the alternate screen before stderr output is not observable from the diff alone, and no CI has exercised it. This is a sponsored run (the author lacks write access); a maintainer's @qwen-code /verify comment approves the head it was written against, and the run carries a pre-execution risk screen plus a full workspace wipe. Read the resulting report with the same skepticism as the fork's own CI logs.

中文说明

独立方案: 针对 VP 模式静默退出问题,我会对上游现有代码做五处小改动:在 cli.tsuncaughtException handler 中加同步 debug log 写入、写 stderr 前先退出 alternate screen、在现有 ErrorBoundary.tsx 中加 consumeLastRenderError()、添加 SIGHUP handler、移除 kittyProtocolDetector.ts 中的 SIGTERM/SIGINT 监听。大约五个文件、100 行代码。

与 PR 的对比: PR 的修复逻辑与这个方案高度吻合——思路都是对的。但实现在 fork 的 gemini.tsx 中而非上游的 cli.ts + startInteractiveUI.tsx,且被 1149 个文件的 fork 分叉所淹没。

修复代码本身的发现:

  • PR 的 ErrorBoundary.tsx回退上游版本——丢失了 sanitizeTerminalText()(防止终端转义注入)。真正新的部分(consumeLastRenderError())是干净的,可以几行代码加到上游现有文件中。
  • setupUncaughtExceptionHandler 结构良好,但上游的 handler 在 cli.ts 中且处理了 PTY 竞争抑制。聚焦的 PR 应扩展现有 handler 而非在另一个文件中添加并行版本。
  • kittyProtocolDetector.ts 的改动最直接适用于上游。
  • 测试写得很好(7 个测试覆盖全面)。
  • debugLogger.error() 添加到 setupUnhandledRejectionHandler 是一个好改动,一行代码即可适用于上游。

测试: 无构建/lint/类型检查/单元测试 CI 运行。无证据表明此 diff 能针对上游 main 编译。

Qwen Code · qwen3.8-max-preview

Reviewed at 476dfd06c1165f4ef9cf224db5bb85dd72b1faeb · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — the investigation is thorough and the fix ideas are sound, but the PR as structured cannot merge: it's a 250-commit fork divergence that would overwrite upstream code with a less secure version and target architectural entry points that don't exist upstream.

Stepping back: the crash analysis here is genuinely good work. The stderr-to-alternate-screen silence mechanism, the sync-vs-async debug log insight, the kitty signal-handler race — these are real findings from real debugging, not theoretical hardening. The five incremental improvements I called out in Stage 1 are worth upstreaming.

But this PR is not the vehicle for that. Three blocking issues:

  1. ErrorBoundary.tsx regresses upstream's security. The PR's version drops sanitizeTerminalText() from the error message render path. Upstream added that to prevent terminal escape injection through crafted error messages — a model or tool that throws Error('\x1b[...') could write arbitrary escape sequences to the user's terminal. Merging this file as-is silently removes that protection.

  2. Architectural mismatch. The fork's gemini.tsx inlines the interactive UI setup; upstream delegates to startInteractiveUI.tsx and cli.ts. The PR's setupUncaughtExceptionHandler and ErrorBoundary wrapping are in the wrong file for upstream. A focused PR would extend the existing uncaughtException handler in cli.ts (which already handles PTY-race suppression) and add consumeLastRenderError() to the existing ErrorBoundary.tsx.

  3. 1,149 files of fork divergence. .aoneci/ CI scripts, .fork/ patch infrastructure, DataWorks branding, channel packages, and extensive core refactors — none of which belong in a fix for silent VP-mode crashes. No CI has run to verify this diff compiles against upstream.

The path forward is straightforward: close this PR, rebase on current main, and submit a focused PR with the five incremental improvements. The kitty signal-handler removal (kittyProtocolDetector.ts) and the debugLogger.error() addition to setupUnhandledRejectionHandler are near-drop-in patches. The consumeLastRenderError pattern needs a small addition to upstream's existing ErrorBoundary.tsx. The uncaughtException handler improvements (sync debug log + alternate-screen exit) extend the existing handler in cli.ts.

Requesting changes — not because the ideas are wrong, but because the PR needs restructuring before it can be reviewed and merged meaningfully.

中文说明

信心:2/5 — 调查很深入,修复思路也很好,但 PR 的结构决定了它无法合入:这是一个 250 commit 的 fork 分叉,会用安全性更低的版本覆盖上游代码,并且针对上游不存在的架构入口。

三个阻塞问题:

  1. ErrorBoundary.tsx 回退了上游的安全性——丢失了 sanitizeTerminalText()(防止终端转义注入)。
  2. 架构不匹配——fork 的 gemini.tsx 内联了交互式 UI 设置,上游委托给 startInteractiveUI.tsxcli.ts
  3. 1149 个文件的 fork 分叉——与 VP 模式崩溃修复无关。

建议路径: 关闭此 PR,基于当前 main rebase,提交一个聚焦的 PR,包含五项增量改进。kittyProtocolDetector.ts 的信号处理器移除和 setupUnhandledRejectionHandlerdebugLogger.error() 添加几乎可以直接应用。consumeLastRenderError 需要对上游现有 ErrorBoundary.tsx 做小改动。uncaughtException handler 的改进(同步 debug log + alternate screen 退出)扩展 cli.ts 中的现有 handler。

请求修改——不是因为思路错误,而是 PR 需要重构才能被有意义地审查和合入。

Qwen Code · qwen3.8-max-preview

Reviewed at 476dfd06c1165f4ef9cf224db5bb85dd72b1faeb · re-run with @qwen-code /triage

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

Needs restructuring — see my notes above. The fix ideas are solid, but the PR is a 250-commit fork divergence that would overwrite upstream's ErrorBoundary (losing sanitizeTerminalText) and target the wrong architectural entry points. A focused PR on current main with the five incremental improvements would be very welcome. 🙏

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.

7 participants