qwen-code/packages/cli
John London b1d33dbda2
fix(cli): preserve comments and formatting in settings.json during migration write-back (#3861)
* fix(cli): preserve comments and formatting in settings.json during migration

The persistSettingsObject() helper in loadAndMigrate() previously used
writeWithBackupSync() with raw JSON.stringify(), which stripped all comments
and custom formatting from users' settings.json on every migration or version
normalization.

Fix: Refactored updateSettingsFilePreservingFormat() in commentJson.ts with:

1. Sync mode (sync=true): Removes keys from the original file that are not
   present in the migrated object, preventing zombie keys from persisting
   after migrations that remove deprecated settings.

2. Atomic writes: Replaced fs.writeFileSync with a writeFileSyncAtomic()
   helper that uses temp-file + rename for crash-safe writes. This applies
   to both the migration path (persistSettingsObject) and the runtime
   setValue path (saveSettings).

3. Comment preservation: Uses comment-json's parse() during the load phase,
   so comment metadata is retained in the parsed structure and preserved by
   stringify() during write-back. Keys that exist in both the original file
   and the migrated object keep their original comments.

persistSettingsObject now calls updateSettingsFilePreservingFormat with
{sync: true} to get all three guarantees: comment preservation, zombie key
removal, and atomic writes.

The test file updates (settings.test.ts, commentJson.test.ts) from the
previous iteration are reverted since the write mechanism now goes through
the same writeFileSyncAtomic path.

Fixes #3843

* fix(cli): remove nested zombie keys during migration sync

The sync mode in updateSettingsFilePreservingFormat only removed top-level
zombie keys. Nested deprecated keys (e.g. general.disableAutoUpdate) survived
because applyUpdates performed a deep merge without removing keys absent from
the migrated object.

Fix: Added a sync parameter to applyUpdates() that recursively deletes keys
not present in the updates object at every nesting level. This ensures
deprecated keys like general.disableAutoUpdate are properly cleaned up during
V2→V3 migration write-back.

Added 3 new tests:
- Nested zombie keys removed in sync mode
- Top-level zombie keys removed in sync mode
- Unrelated keys in nested objects preserved during sync

* fix(pr-3861): address review comments on settings.json comment preservation

- Replace writeFileSyncAtomic with writeWithBackupSync from writeWithBackup.ts
  to eliminate duplicate atomic-write implementations
- Simplify UpdateSettingsOptions interface to a plain boolean `sync` parameter
- Include parse error details in stderr output (error.message with position info)
- Add test for written=false path in settings.test.ts
- Add test for sync=true with empty updates={} documenting zombie key removal
- Mock statSync in settings test to support writeWithBackupSync directory check

* fix(pr-3861): add debugLogger mock and assert error on written=false

- Add mockDebugLogger with debug/warn/error/info to settings.test.ts
- Enhance existing written=false test to assert debugLogger.error is called
- All 96 settings tests + 15 commentJson tests pass
2026-05-09 12:36:49 +08:00
..
src fix(cli): preserve comments and formatting in settings.json during migration write-back (#3861) 2026-05-09 12:36:49 +08:00
index.ts fix(cli): stop double-wrapping and double-printing API errors in non-interactive mode (#3749) 2026-05-03 08:39:31 +08:00
package.json chore(release): v0.15.9 [skip ci] 2026-05-08 22:46:11 +08:00
test-setup.ts fix: prevent bogus shell permission rules in tests 2026-03-20 17:55:33 +08:00
tsconfig.json Add background agent resume and continuation (#3739) 2026-05-01 12:14:33 +08:00
vitest.config.ts refactor(core): Unify package exports and improve dev experience 2026-02-01 11:59:05 +08:00