Skip to content

feat(cli): Add settings JSON corrupted warning dialog#4560

Merged
wenshao merged 26 commits into
QwenLM:mainfrom
zzhenyao:fix/cli-settings-corrupt-warning
May 31, 2026
Merged

feat(cli): Add settings JSON corrupted warning dialog#4560
wenshao merged 26 commits into
QwenLM:mainfrom
zzhenyao:fix/cli-settings-corrupt-warning

Conversation

@zzhenyao

@zzhenyao zzhenyao commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

When the user-level settings.json contains invalid JSON, the CLI silently loads backup settings or enters the initial settings screen. This PR adds a fault-tolerant mechanism: automatic settings recovery + a UI warning dialog.

Fixes #4448

Problem Discovery

  1. Writing invalid JSON into settings.json and starting the CLI caused the file to be silently overwritten without user notification
  2. Tracing loadSettings() revealed that on JSON parse failure, the corrupted file is renamed (moved) and recovery from .orig backup is attempted
  3. The recovery logic uses fs.renameSync which moves the original file — it should use fs.copyFileSync to preserve a copy for the user to inspect
  4. A deeper issue: relaunchAppInChildProcess() forks the child process, and the child re-reads settings.json (already repaired by the parent), causing the corruption marker to be lost
  5. Comparison test: without .orig backup the dialog shows normally (child also detects the error), with backup the dialog does not appear (parent already fixed the file)

Solution

Core Idea

Pass corruption state between parent and child processes via environment variables, avoiding reliance on filesystem state. Skipping relaunchAppInChildProcess() would affect existing logic, so this approach was not adopted.

Changes

1. Recovery Logic (settings.ts)

  • fs.renameSyncfs.copyFileSync: preserve a copy of the corrupted file (.corrupted suffix)
  • Add corruptedPath and wasRecovered fields to LoadedSettings
  • On successful parse, read and consume corruption state from environment variables (User scope only, deleted after reading)

2. Environment Variable Propagation (gemini.tsx)

  • When settings.corruptedPath is detected, set QWEN_CODE_SETTINGS_CORRUPTED_PATH and QWEN_CODE_SETTINGS_WAS_RECOVERED
  • Child processes inherit these env vars, so the marker is not lost even after re-reading settings.json
  • These env vars are deleted after being read in loadSettings() and after startInteractiveUI() to prevent stale state propagating to child processes

3. UI Dialog (App.tsx + SettingsCorruptedDialog.tsx)

  • Rendered at the top of the App, taking precedence over all other UI
  • Wrapped in <Box marginX={2}> container, consistent with DialogManager's parent in DefaultAppLayout
  • Manual key handling (avoids RadioButtonSelect delay)
  • Two options: Exit (restore corrupted file and exit) / Continue (use recovered settings or empty settings)

Design Decisions

  • Dialog style matches the IDE integration dialog (marginLeft={1}, green selection, red title/border)
  • Continue button text varies based on recovery state
  • Ctrl+C maps to Exit, ESC closes the dialog and continues to the main UI
  • Dismissed state prevents repeated display

Test Results

npm run preflight

Test Files  47 passed (47)
        Tests  419 passed | 1 skipped (420)
     Start at  08:10:42
     Duration  6.04s (transform 5.27s, setup 0ms, collect 49.98s, tests 2.56s, environment 3.30s, prepare 4.72s)

Screenshots

with backup (.orig exists)

1

without backup, dialog shown

3

without backup, after selecting "Continue"

2

TODO

  • Register i18n strings in locale files for settings.corrupted UI translation
  • Update related documentation

@zzhenyao
zzhenyao marked this pull request as ready for review May 27, 2026 00:41
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/ui/App.tsx Outdated
Comment thread packages/cli/src/ui/components/SettingsCorruptedDialog.tsx
Comment thread packages/cli/src/ui/components/Notifications.tsx Outdated
Comment thread packages/cli/src/ui/App.tsx

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] 3 existing tests in settings.test.ts fail because they assert the old renameSync + .corrupted.<timestamp> behavior:

  1. "should handle JSON parsing errors gracefully by renaming corrupted file" — asserts renameSync called with .corrupted. pattern
  2. "should degrade gracefully when both settings.json and backup are corrupted" — same renameSync assertion
  3. "should start with empty settings when rename of corrupted file fails" — mocks renameSync to throw, asserts warning contains "fix the JSON" (old message)

All three must be rewritten to assert copyFileSync (not renameSync), the new .corrupted path (no timestamp), and the new warning text ("Your settings have been reset.").

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/ui/App.tsx Outdated
Comment thread packages/cli/src/ui/components/SettingsCorruptedDialog.tsx

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Test failures (10 tests, 3 CI checks):

  • App.test.tsx: 7 tests crash — useSettings() added to App but renderWithProviders lacks SettingsContext.Provider (see existing inline comment at App.tsx:20).
  • settings.test.ts: 3 tests fail — assertions still reference fs.renameSync + .corrupted.<timestamp> pattern, but PR switched to fs.copyFileSync + fixed .corrupted suffix. Affected tests: "should handle JSON parsing errors gracefully" (~line 1884), "should degrade gracefully when both settings.json and backup are corrupted" (~line 1965), "should start with empty settings when rename of corrupted file fails" (~line 1998).

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/ui/App.tsx Outdated
Comment thread packages/cli/src/config/settings.ts

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No new Critical findings. 7 new Suggestions below (in addition to R1/R2 comments). Downgraded from Approve to Comment: CI failing (Test on macos-latest, windows-latest, ubuntu-latest). — qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/ui/components/SettingsCorruptedDialog.tsx
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/ui/components/SettingsCorruptedDialog.tsx
Comment thread packages/cli/src/config/settings.ts Outdated
@zzhenyao

Copy link
Copy Markdown
Contributor Author
  • Added existsSync guards in App.tsx onExit/onContinue — file may not exist if all save attempts failed.
  • Added .corrupted cleanup in onContinue — no longer needed once user continues.
  • Bound Escape key to Continue in SettingsCorruptedDialog — consistent with existing dialog behavior.
  • Added full test coverage for SettingsCorruptedDialog keyboard navigation, both wasRecovered variants, callback assertions.
  • Filtered startup warnings in Notifications.tsx to avoid duplicate display — corruption dialog already handles the settings file warning, so redundant notifications are now suppressed.
    
    
    @wenshao Most review comments have been addressed. Could you take another look when convenient?
    
    
    

@zzhenyao
zzhenyao requested a review from wenshao May 27, 2026 07:02
@wenshao

wenshao commented May 27, 2026

Copy link
Copy Markdown
Collaborator

E2E 验证报告 — PR #4560

环境:Debian GNU/Linux 13 (trixie) · Linux 6.12 x86_64 · Node v22.22.2 · tmux 3.5a · 新 worktree qwen-code-pr4560(HEAD 841901eeb)完整 npm ci 后 bundle。

为避免污染本机真实的 ~/.qwen,所有 E2E 全部通过 QWEN_HOME=/tmp/pr4560-tests/<scenario>/qwen-home 重定向到独立目录跑,干净可复现。

1. 单元 / typecheck

模块 结果
packages/cli/src/config/settings.test.ts 108 / 108 ✅
packages/cli/src/ui/components/SettingsCorruptedDialog.test.tsx 8 / 8 ✅
packages/cli/src/ui/App.test.tsx 7 / 7 ✅
npm run typecheck(所有 workspaces) 0 error ✅

2. E2E 场景

CLI 入口:node /root/git/qwen-code-pr4560/dist/cli.js,在 tmux session pr4560 中真实交互。

2.1 Scenario A — 用户 settings 损坏,.orig 备份

设置:$QWEN_HOME/settings.json = "this is NOT valid JSON {",无任何 .orig

启动后立即在 banner 下方渲染对话框:

╭─ > Settings file corrupted ──────────────────────────────────────────────────╮
│ Your settings file had invalid JSON. A copy of the corrupted file has been   │
│ saved for reference.                                                         │
│ /tmp/pr4560-tests/scenarioA/qwen-home/settings.json.corrupted                │
│                                                                              │
│ > Exit and restore corrupted file                                            │
│   Continue with empty settings (esc)                ← "empty" 文案分支正确  │
╰──────────────────────────────────────────────────────────────────────────────╯

磁盘:

  • settings.json 25 bytes(损坏内容原封不动) ✅
  • settings.json.corrupted 25 bytes(与原文件同内容 — copyFileSync 语义正确,而不是 renameSync)✅
  • .orig

Esc(Continue 路径)后:

  • settings.json.corruptedunlinkSync 删除 ✅
  • 对话框消失,正常进入 Provider 选择界面
  • 在通知栏看到 "invalid JSON" 的重复 warning ✅(Notifications.tsx 的 dedup 过滤生效)

2.2 Scenario B — 用户 settings 损坏,有有效 .orig 备份(关键 bug fix)

这是 #4448 描述的"对话框被静默吞掉"的核心 case:在旧代码里,父进程从 .orig 恢复后写回 settings.json,子进程再次 loadSettings() 就只看到合法 JSON,于是 dialog 不再出现。

设置:

  • $QWEN_HOME/settings.json.orig = 完整的合法 JSON(99 bytes)
  • $QWEN_HOME/settings.json = "this is NOT valid JSON {"(25 bytes)

启动后:

> Settings file corrupted
... settings.json.corrupted
> Exit and restore corrupted file
  Continue with recovered settings (esc)   ← "recovered" 文案,证明 wasRecovered=true 透传到 UI

磁盘断言:

文件 大小 内容 期望
settings.json 99 备份恢复后的合法 JSON
settings.json.corrupted 25 原始损坏内容(保留供用户排查)
settings.json.orig 99 备份原封不动

env var 跨子进程透传是这个 case 的隐式断言:父进程 loadSettings() 已经把 settings.json 写回合法内容,relaunchAppInChildProcess() fork 出来的子进程 JSON.parse(stripJsonComments(content)) 一定成功 —— 此时对话框还能出现,只能是 因为 QWEN_CODE_SETTINGS_CORRUPTED_PATH / QWEN_CODE_SETTINGS_WAS_RECOVERED 这两个 env var 把损坏标记带过了 fork 边界(settings.ts L952-960)。这条路径 == PR 的核心修复。✅

2.3 Scenario C — Exit 路径:还原损坏文件 + exit 1

承接 Scenario B 状态(settings.json 已是 99 bytes 恢复内容),在 dialog 中按 Enter(默认选中 Exit):

$ echo "EXIT_CODE=$?"
EXIT_CODE=1                                                ✅
$ ls -la $QWEN_HOME/settings.json*
-rw-r--r-- 25  settings.json              ← 被 .corrupted 覆盖回原始损坏内容 ✅
-rw-r--r-- 25  settings.json.corrupted    ← 仍保留 ✅
-rw-r--r-- 99  settings.json.orig         ← 不动 ✅
$ cat $QWEN_HOME/settings.json
this is NOT valid JSON {                                   ✅

App.tsx L36-52 的语义就是这条:"Exit and restore corrupted file" = 把 .corrupted 拷回 settings.json,让下次启动用户能在原位置看到自己写错的内容,然后 process.exit(1)

2.4 Scenario D — Continue 路径在 Scenario A 已覆盖

承接 A 中按 Esc 后:

  • .corrupted 文件被删除 ✅
  • dismissed=true 之后正常渲染 DefaultAppLayout(Provider 选择界面)✅

2.5 Scenario E — workspace 级别 settings 损坏 不应 弹对话框

设置:

  • $QWEN_HOME/settings.json = 合法 JSON
  • $CWD/.qwen/settings.json = "this is WORKSPACE NOT valid JSON {"

启动后:

(没有红框对话框)
╭─ Warning: Settings file .../scenarioE/work/.qwen/settings.json has invalid JSON. Your settings have been reset. ─╮
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
(继续进入 Provider 选择)
  • 红框 SettingsCorruptedDialog 出现 ✅
  • workspace 的 .qwen/settings.json.corrupted 被创建 ✅(recovery 逻辑对任意 scope 都跑,磁盘文件仍然保留)
  • 只在 settings.ts L1180-1181 把 userResult.corruptedPath 抽出来透传到 LoadedSettings,所以非 User scope 不会触发 dialog(仅经标准 Notification 系统提示一次 warning)✅

2.6 Sanity — 二次启动不应再弹

承接 B 之后手动把 settings.json 改为合法、删掉 .corrupted,再次 node $QWEN_CLI

$ env | grep QWEN_CODE_SETTINGS
    (空)                                  ← env var 没遗留 ✅
$ node $QWEN_CLI
(直接进入 Provider 选择,无对话框)        ← 干净启动 ✅

证明 settings.ts L958-959 delete process.env['QWEN_CODE_SETTINGS_CORRUPTED_PATH'] 的清理是工作的,不会在合法启动时误触发。

3. 小问题(不阻塞 merge)

PR 描述与代码行为不一致:PR 描述写

ESC/Ctrl+C maps to the first option (Exit), following convention

SettingsCorruptedDialog.tsx L31-38、对应单元测试 L174 + L197、以及对话框自带的 Continue with ... (esc) 文案三处都是:

  • ESC → onContinue
  • Ctrl+C → onExit

代码 + 测试 + UI 文案三者一致,建议把 PR 描述中的这一句改成 "ESC continues with recovered/empty settings; Ctrl+C exits and restores the corrupted file",避免后续 reviewer 按字面意思去读源码时被绊一下。

另外两个 PR 自述的 TODO 仍然是 follow-up:i18n 注册 + 文档更新;本次未在测试范围内。

4. 结论

  • 单元:123 / 123 通过,typecheck clean
  • 6 个 E2E 场景(A 无备份、B 有备份的核心 fix、C Exit 还原、D Continue 清理、E workspace 不弹、Sanity 二次启动)全部通过
  • 关键不变量已落实:
    • fs.copyFileSync 而不是 renameSync —— .corrupted 文件磁盘可见 ✅
    • env var 跨 relaunchAppInChildProcess fork 边界透传 ✅
    • 子进程消费后立刻 delete env var,避免后续 loadSettings 误触发 ✅
    • 仅 User scope 触发 dialog,workspace 走 Notification ✅
    • Exit/Continue 双路径磁盘副作用与代码一致 ✅
  • 唯一遗留:PR 描述里 ESC/Ctrl+C 语义那句与代码相反,建议在 merge 前更正文案
  • 跨平台留白:Linux x86_64 / Node 22 已覆盖
  • 从 maintainer 视角,建议 merge(文案勘误可以放在合并后小 patch 处理)

完整测试痕迹:tmux session pr4560(3 windows),磁盘工件保留在 /tmp/pr4560-tests/

Comment thread packages/cli/src/config/settings.test.ts Outdated
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/ui/components/Notifications.tsx Outdated

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Downgraded from Request Changes to Comment: CI failing (Test (macos-latest, Node 22.x), Test (windows-latest, Node 22.x)). 1 new Critical + 2 new Suggestions below.

Additional note (not inline due to overlap): App.tsx:51process.exit(1) uses error exit code for a user-initiated recovery action. Consider process.exit(0) since the user explicitly chose to exit. — qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/ui/App.test.tsx
Comment thread packages/cli/src/gemini.tsx
Comment thread packages/cli/src/ui/components/Notifications.tsx Outdated
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/ui/App.test.tsx
Comment thread packages/cli/src/config/settings.test.ts
@zzhenyao

Copy link
Copy Markdown
Contributor Author

@wenshao Critical items have all been addressed. Suggestions are partially addressed.

Fixed. Removed the filter in Notifications.tsx since the corruption warning is no longer added to migrationWarnings when the dialog is shown, so there's nothing to filter. Also added truncateToItem to the mock.

For the env var issue - added !corruptedSaved guard so the child process doesn't trigger a normalization write after recovering from corruption. Env vars are cleaned up after startInteractiveUI and in loadSettings after being read. Added tests for the env var propagation.

Updated the PR description with changes and screenshots.

@zzhenyao
zzhenyao requested a review from wenshao May 27, 2026 10:06
Comment thread packages/cli/src/gemini.tsx
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/ui/App.tsx
Comment thread packages/cli/src/config/settings.test.ts
@zzhenyao
zzhenyao requested a review from wenshao May 27, 2026 11:02
Comment thread packages/cli/src/config/settings.test.ts Outdated
@zzhenyao

Copy link
Copy Markdown
Contributor Author

@wenshao Resolved the merge conflicts and cleaned up the comment. Could you please take another look? I'll re-request review now.

@zzhenyao
zzhenyao requested a review from wenshao May 27, 2026 13:37
: '0';
}
await cleanupCheckpoints();
// Performance checkpoint

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.

[Critical] Env vars pre-consumed by validateAuthMethod() before sandbox launches.

When sandbox is configured, validateAuthMethod() (line ~507) internally calls loadSettings() (auth.ts:169), whose env-var propagation block (settings.ts:960-966) reads and deletes both QWEN_CODE_SETTINGS_CORRUPTED_PATH and QWEN_CODE_SETTINGS_WAS_RECOVERED from process.env. By the time start_sandbox() runs (~line 591), the corruption markers are gone — the sandbox child process inherits clean env vars, re-reads the already-recovered settings.json, and never shows the corruption dialog.

This affects the common case: sandbox configured + auth type explicitly set + useExternal: false. The non-sandbox path (relaunchAppInChildProcess) works correctly because no intermediate loadSettings() call occurs between env var setup and child fork.

Suggested fix: Move the env var setup to immediately before each child-launch call site (start_sandbox and relaunchAppInChildProcess), or make loadSettings() accept a skipEnvVarPropagation parameter that intermediate callers like validateAuthMethod can pass.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Added consumeCorruptionEnvVars optional parameter to loadSettings() (defaults true, no behavior change for existing callers). validateAuthMethod() passes false so it doesn't consume the corruption env vars before start_sandbox() inherits them. Added test coverage for both the new parameter and the scope guard.

return {
settings: {},
migrationWarnings: [warningMsg],
corruptedPath: corruptedSaved ? corruptedPath : undefined,

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.

[Suggestion] No-backup path emits corruption warning through BOTH the dialog AND migrationWarnings.

migrationWarnings: [warningMsg] is set unconditionally alongside corruptedPath. In interactive mode, the dialog renders (conveying "Your settings file had invalid JSON"), and after dismissal the same message reappears as a startup notification via getSettingsWarnings()startupWarningsNotifications.

This is inconsistent with the backup-recovery path (line ~1046), which does NOT include the recovery message in migrationWarnings.

Suggested fix: Filter corruption-specific warnings from startupWarnings in gemini.tsx when the dialog will render:

const filteredWarnings = settings.corruptedPath
  ? startupWarnings.filter((w) => !w.includes('has invalid JSON'))
  : startupWarnings;

Or restructure the no-backup return to separate the corruption warning from migration warnings.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Corruption warnings now go through corruptedPath only, with early stderr emitting directly from the startup path in gemini.tsx.

});

it('should only consume env vars for SettingScope.User', () => {
(mockFsExistsSync as Mock).mockImplementation(

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.

[Suggestion] This test doesn't exercise the scope === SettingScope.User guard.

The test doesn't set QWEN_CODE_SETTINGS_CORRUPTED_PATH or QWEN_CODE_SETTINGS_WAS_RECOVERED in process.env — it only mocks existsSync for the workspace path and asserts corruptedPath is undefined. Since no env vars are present, the scope guard at settings.ts:961 (if (envCorruptedPath && scope === SettingScope.User)) is never exercised. A bug that removed the scope check would not be caught.

Suggested fix: Set the env vars before calling loadSettings(), then verify user scope consumed them while workspace scope did not:

process.env['QWEN_CODE_SETTINGS_CORRUPTED_PATH'] = '/test/path.corrupted';
process.env['QWEN_CODE_SETTINGS_WAS_RECOVERED'] = '1';
const result = loadSettings(MOCK_WORKSPACE_DIR);
expect(result.corruptedPath).toBeDefined(); // user scope consumed
expect(process.env['QWEN_CODE_SETTINGS_CORRUPTED_PATH']).toBeUndefined(); // deleted

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@zzhenyao

Copy link
Copy Markdown
Contributor Author

Fixed a security vulnerability where QWEN_CODE_SETTINGS_CORRUPTED_PATH and QWEN_CODE_SETTINGS_WAS_RECOVERED environment variables could be injected via project .env files, allowing malicious repos to trigger fake corruption dialogs.

  1. Added the two environment variables to PROJECT_ENV_HARDCODED_EXCLUSIONS to block injection from project .env files
  2. Extracted environment variable names as constants, eliminating duplicated string literals across the codebase
  3. Validated that inherited env var paths match the locally computed corruptedPath before showing the corruption dialog
  4. Split onExit and onContinue try/catch blocks so error messages accurately reflect which operation failed

Critical issues have been addressed. Multiple Suggestions have been implemented as well. Could you take another look when convenient?

@zzhenyao
zzhenyao requested a review from wenshao May 28, 2026 02:10
wenshao
wenshao previously approved these changes May 28, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

R12b Critical (env var injection via .qwen/.env) is fully resolved with a sound two-layer defense: (1) ENV_CORRUPTED_PATH/ENV_WAS_RECOVERED added to PROJECT_ENV_HARDCODED_EXCLUSIONS, (2) envCorruptedPath === corruptedPath equality check ensures the env var value matches the locally-computed path. Ordering is correct — loadAndMigrate consumes env vars before loadEnvironment reads project .env files.

The App.tsx error-handling refactor (split try/catch for copyFileSync/unlinkSync, stderr logging in onContinue) preserves exit semantics and improves observability.

One low-confidence suggestion (terminal-only, not blocking): consider adding a negative test asserting that a mismatched ENV_CORRUPTED_PATH value is rejected by the equality check — the positive path is covered but the new guard branch lacks negative coverage.

— qwen3.7-max via Qwen Code /review

@zzhenyao

Copy link
Copy Markdown
Contributor Author

@wenshao Thanks for the detailed review and approval!

Comment thread packages/cli/src/gemini.tsx Outdated
Comment thread packages/cli/src/config/settings.ts
Comment thread packages/cli/src/config/settings.ts
@zzhenyao

Copy link
Copy Markdown
Contributor Author

R13 fixed:

1: pulled clearCorruptionEnvVars() out as a helper in gemini.tsx. The three identical delete pairs were annoying to keep in sync.

2: added a comment on the needsMigration branch in settings.ts to explain why it skips the !corruptedSaved guard. Migrations need to run on recovered settings; version normalization creates .orig backups we don't want.

3: added negative test for mismatched ENV_CORRUPTED_PATH. The === guard was new and had no regression protection.

@zzhenyao

Copy link
Copy Markdown
Contributor Author

@wenshao I’ve addressed the latest feedback and pushed an update. Could you take another look when convenient?

@wenshao

wenshao commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Maintainer Verification Report

Reviewer: wenshao
Branch: fix/cli-settings-corrupt-warningmain
Head commit: 6cb6a64 (fix(cli): harden corruption env var guard with helper, comment, and regression test)
Environment: macOS Darwin 25.4.0, Node.js v22.17.0

Test Results

Focused tests (PR-touched files):

Test Suite Result
settings.test.ts 118/118 passed
SettingsCorruptedDialog.test.tsx 8/8 passed
App.test.tsx 7/7 passed

Full npm run preflight:

Package Test Files Tests Result
cli 387 passed 6952 passed, 9 skipped PASS
core 344 passed, 1 failed 9531 passed, 1 failed, 4 skipped 1 pre-existing failure*
sdk 13 passed 422 passed PASS
vscode-ide-companion 47 passed 419 passed, 1 skipped PASS

*The single failure is anthropicContentGenerator.test.ts — a User-Agent header assertion ('claude-cli/1.2.3' vs expected 'QwenCode/1.2.3') introduced by commit 62ed44e1f on main, unrelated to this PR.

Build & Typecheck

Check Result
npm run build PASS (0 errors)
tsc --noEmit (cli) PASS
tsc --noEmit (core) PASS
tsc --noEmit (acp-bridge) PASS
Lint 15 pre-existing warnings, 0 errors

Code Review Notes

Correctness observations:

  • fs.renameSyncfs.copyFileSync correctly preserves the corrupted file at .corrupted so users can inspect and the onExit handler can restore it
  • Env var propagation (QWEN_CODE_SETTINGS_CORRUPTED_PATH, QWEN_CODE_SETTINGS_WAS_RECOVERED) correctly bridges parent→child across relaunchAppInChildProcess() boundary
  • consumeCorruptionEnvVars=false parameter in validateAuthMethod correctly prevents early consumption of env vars before the main startup path reads them
  • PROJECT_ENV_HARDCODED_EXCLUSIONS inclusion of corruption env vars prevents .env injection attacks
  • Guard in loadSettings rejects mismatched ENV_CORRUPTED_PATH (must match expected ${filePath}${CORRUPTED_SUFFIX}) — prevents malicious env injection from setting arbitrary corruption paths
  • clearCorruptionEnvVars() called in all exit paths (interactive + non-interactive) prevents stale state

UI behavior:

  • Dialog renders at top of App, blocks other UI when showing
  • dialogsVisible flag updated to include corruption dialog state — prevents global keypress interference
  • Ctrl+C → onExit (restore + exit), ESC → onContinue (dismiss), up/down arrow navigation

Potential improvement (non-blocking):

  • The t() calls for dialog strings are placeholders since i18n registration is listed as a TODO

Verdict

PASS — Ready to merge. All PR-introduced code passes tests, build/typecheck clean, env var security hardened, no regressions.

Comment thread packages/cli/src/gemini.tsx Outdated
@zzhenyao
zzhenyao requested a review from wenshao May 28, 2026 10:13
@zzhenyao

Copy link
Copy Markdown
Contributor Author

the R14 fix commit (12f864f), I added a regression test to verify the afterSpawn callback. the test had a mock issue: vi.mock('node:child_process') created two separate vi.fn() instances so the mock was never actually called. fixed by sharing a single mockSpawn between both exports.

this commit (b447572) only touches the test file, no code change.

@wenshao Could you take another look when convenient?

@zzhenyao

Copy link
Copy Markdown
Contributor Author

Hi @wenshao, I’ve pushed a commit to address the R14 comments, which automatically dismissed your previous approval. CI is green now, could you take another look when you have a chance?  I’m happy to fix anything that still needs work.

wenshao
wenshao previously approved these changes May 30, 2026
Comment thread packages/cli/src/utils/relaunch.ts Outdated
@zzhenyao

Copy link
Copy Markdown
Contributor Author

Applied the suggestion, thanks. Could you take another look?

@zzhenyao

Copy link
Copy Markdown
Contributor Author

@wenshao Thanks for your approval! If needed, could you help invite another reviewer for this PR?🙏

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

LGTM overall. The env-var propagation across relaunchAppInChildProcess() is a clean solution to the parent/child state-loss problem.

A couple of non-blocking follow-ups for later:

  • i18n: the t() calls are passthrough right now — register the locale keys when convenient
  • Test isolation: the cross-test stdin pollution between SettingsCorruptedDialog.test.tsx and App.test.tsx is worth a follow-up fix to avoid flakiness in future CI matrix expansions

Nice work on the E2E coverage 👍 @zzhenyao

@wenshao
wenshao merged commit d074ed8 into QwenLM:main May 31, 2026
10 of 15 checks passed
@zzhenyao

Copy link
Copy Markdown
Contributor Author

@yiliang114 Appreciate the approval!🙏
I’ll follow up with separate PRs for those.

@zzhenyao

zzhenyao commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@tanzhenxin, @pomelo-nwu Hi, is issue #2136 still on the roadmap? I noticed it's currently in the "To Do" column on the project board. I'd like to pick it up.

My plan is to first refactor the existing SettingsCorruptedDialog into a generic WarningDialog component that separates UI logic (keyboard navigation, option rendering) from business logic. Then I'll reuse this component for both home directory (~) and root directory (/) warnings, making them dismissible with Continue/Exit options.

I'll submit this in two PRs:

  1. Refactor: Extract WarningDialog from SettingsCorruptedDialog (pure refactor, no behavior change)
  2. Feature: Add interactive home/root directory warning dialogs using the shared WarningDialog component
    

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.

[Request] Improve what happens if settings.json is invalid

3 participants