#13849: fix(configure): preserve custom config keys across wizard sections
commands
size: M
trusted-contributor
experienced-contributor
Cluster:
Config Redaction Improvements
## Summary
Fixes #9632
The configure wizard was overwriting the entire config file when modifying a single section, dropping custom keys the wizard doesn't manage (e.g., `tools.media.audio`, `gateway.auth.allowTailscale`, disabled `skills.entries`).
**Root cause**: The wizard reads config through `readConfigFileSnapshot()`, which returns a Zod-validated + defaults-applied version. This processed config was used as the base for writes, meaning:
- Zod-processed sections replaced raw file values (adding defaults the user never set)
- `${VAR}` env references were substituted with literal values
- `$include` directives were resolved to inline content
**Fix**: Added `mergeWizardOutput()` which uses reference equality between `baseConfig` and `nextConfig` to detect which top-level sections the wizard actually modified. Only wizard-modified sections use the wizard's output; unmodified sections preserve their raw file values from `snapshot.parsed`.
- Unmodified sections keep raw file values (no injected defaults)
- `${VAR}` references preserved in unmodified sections
- `$include` at top level falls back to current behavior (separate concern)
- Both local and remote wizard paths use the merge function
## Test plan
- [x] `mergeWizardOutput` unit tests: preserves raw values, uses wizard values for modified keys, preserves keys not in nextConfig, falls back with `$include`, adds new wizard keys
- [x] Integration tests: preserves tools/skills/logging when configuring only `model`, `gateway`, or interactive loop
- [x] Edge case: handler that drops keys → recovered from rawParsed
- [x] Edge case: rawParsed differs from baseConfig (minimal raw vs defaults-injected) → unmodified sections use raw values
- All 11 tests fail before fix, pass after
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR updates the configure wizard to avoid rewriting the entire config when only one wizard section changes. It introduces `mergeWizardOutput()` and uses it in both local/remote wizard flows so that top‑level sections the wizard didn’t modify are preserved from the raw parsed file (keeping user custom keys, `${VAR}` references, and un-expanded `$include` content), while modified sections come from the wizard output. The accompanying tests expand coverage for preserving raw values, recovering keys dropped by section handlers, and `$include` fallback behavior.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- Changes are narrowly scoped to how the wizard merges its output back into the raw parsed config, with explicit handling for default-only sections and `$include` fallback; unit/integration tests cover the key regressions described in the PR.
- No files require special attention
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#16301: fix: preserve custom config keys during configure wizard
by superlowburn · 2026-02-14
88.1%
#21124: fix(config): preserve missing keys from original in restoreRedacted...
by Marvae · 2026-02-19
76.2%
#14179: fix(config): resolve $include directives before validation in setup...
by ken2190 · 2026-02-11
75.3%
#12048: fix: deduplicate config warnings to log once instead of on every re...
by mcaxtr · 2026-02-08
75.0%
#6624: Web Configure Wizard: channels + skills + agent runtime + diff + do...
by Oruga420 · 2026-02-01
74.8%
#21240: fix: GH#20607 prevent doctor from dropping custom config sections
by theognis1002 · 2026-02-19
73.6%
#10258: fix(config): preserve ${ENV_VAR} references when writing config (#9...
by nu-gui · 2026-02-06
72.8%
#10807: fix(config): coerce numeric meta.lastTouchedAt to ISO string
by mcaxtr · 2026-02-07
72.6%
#19510: fix(config): preserve configured values on invalid config validatio...
by yash27-lab · 2026-02-17
72.4%
#20674: fix #20495 & #20515: configure UX + Telegram media group fixes
by neipor · 2026-02-19
72.1%