fix(config): add resolved field to ConfigFileSnapshot for pre-defaults config

The initial fix using snapshot.parsed broke configs with $include directives.
This commit adds a new 'resolved' field to ConfigFileSnapshot that contains
the config after $include and ${ENV} substitution but BEFORE runtime defaults
are applied. This is now used by config set/unset to avoid:
1. Breaking configs with $include directives
2. Leaking runtime defaults into the written config file

Also removes applyModelDefaults from writeConfigFile since runtime defaults
should only be applied when loading, not when writing.
This commit is contained in:
Marcus Castro
2026-02-08 10:59:12 -03:00
committed by Peter Steinberger
parent 9e8d9f114d
commit 3189e2f11b
5 changed files with 48 additions and 12 deletions

View File

@@ -353,6 +353,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: false,
raw: null,
parsed: {},
resolved: {},
valid: true,
config,
hash,
@@ -372,6 +373,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw,
parsed: {},
resolved: {},
valid: false,
config: {},
hash,
@@ -398,6 +400,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw,
parsed: parsedRes.parsed,
resolved: coerceConfig(parsedRes.parsed),
valid: false,
config: coerceConfig(parsedRes.parsed),
hash,
@@ -426,6 +429,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw,
parsed: parsedRes.parsed,
resolved: coerceConfig(resolved),
valid: false,
config: coerceConfig(resolved),
hash,
@@ -445,6 +449,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw,
parsed: parsedRes.parsed,
resolved: coerceConfig(resolvedConfigRaw),
valid: false,
config: coerceConfig(resolvedConfigRaw),
hash,
@@ -460,6 +465,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw,
parsed: parsedRes.parsed,
// Use resolvedConfigRaw (after $include and ${ENV} substitution but BEFORE runtime defaults)
// for config set/unset operations (issue #6070)
resolved: coerceConfig(resolvedConfigRaw),
valid: true,
config: normalizeConfigPaths(
applyTalkApiKey(
@@ -481,6 +489,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
exists: true,
raw: null,
parsed: {},
resolved: {},
valid: false,
config: {},
hash: hashConfigRaw(null),
@@ -507,9 +516,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
}
const dir = path.dirname(configPath);
await deps.fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
const json = JSON.stringify(applyModelDefaults(stampConfigVersion(cfg)), null, 2)
.trimEnd()
.concat("\n");
// Do NOT apply runtime defaults when writing — user config should only contain
// explicitly set values. Runtime defaults are applied when loading (issue #6070).
const json = JSON.stringify(stampConfigVersion(cfg), null, 2).trimEnd().concat("\n");
const tmp = path.join(
dir,