Skip to content

fix(cli): prevent config set/unset from leaking runtime defaults#11949

Merged
steipete merged 4 commits into
openclaw:mainfrom
mcaxtr:fix-6070-config-set-overwrite
Feb 13, 2026
Merged

fix(cli): prevent config set/unset from leaking runtime defaults#11949
steipete merged 4 commits into
openclaw:mainfrom
mcaxtr:fix-6070-config-set-overwrite

Conversation

@mcaxtr

@mcaxtr mcaxtr commented Feb 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #6070

  • config set and config unset now use the resolved config (after $include and ${ENV} substitution) instead of the runtime-merged config with defaults
  • Adds a new resolved field to ConfigFileSnapshot containing the config before runtime defaults are applied
  • Removes applyModelDefaults from writeConfigFile since runtime defaults should only be applied at load time
  • Adds proper redaction for the new resolved field to prevent credential leaks

The bug caused runtime defaults (like agents.defaults.maxConcurrent) to leak into the user's config file whenever config set or config unset was used.

Test plan

  • All 3 new tests in config-cli.test.ts fail before fix, pass after
  • New test for resolved redaction in redact-snapshot.test.ts passes
  • Full test suite passes (5426 tests)
  • pnpm build && pnpm check passes
  • Codex review addressed (security issue with unredacted resolved field)

Changes

  1. src/config/types.openclaw.ts: Added resolved field to ConfigFileSnapshot
  2. src/config/io.ts: Populate resolved field with pre-defaults config; removed applyModelDefaults from writeConfigFile
  3. src/config/validation.ts: Added validateConfigObjectRaw for validation without defaults
  4. src/cli/config-cli.ts: Use snapshot.resolved instead of snapshot.config
  5. src/config/redact-snapshot.ts: Redact the new resolved field
  6. src/config/config.ts: Export validateConfigObjectRaw

Greptile Overview

Greptile Summary

This PR fixes openclaw config set/unset writing back runtime-merged config (including defaults) by introducing a resolved snapshot (post $include + ${ENV} substitution, pre-runtime-defaults) and using that for mutations before persisting. It also removes applying runtime defaults at write time, adds a raw validation entrypoint (validateConfigObjectRaw) to validate without defaults, and updates snapshot redaction to cover resolved so secrets substituted from env don’t leak into logs/snapshots.

These changes fit the existing config pipeline by separating: (1) raw/parsed/resolved config used for persistence and user-facing edits vs (2) runtime config used for execution with defaults and path normalization applied only on load.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk.
  • Changes are narrowly scoped to config snapshot representation and CLI writeback behavior, with accompanying unit tests for the regression and for redaction of the new snapshot field. The updated validation API preserves existing behavior via validateConfigObject while enabling pre-default validation where needed.
  • No files require special attention

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

@openclaw-barnacle openclaw-barnacle Bot added the cli CLI command changes label Feb 8, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 8, 2026

Copy link
Copy Markdown
Contributor
Additional Comments (1)

src/config/io.ts
Defaults still written

writeConfigFile currently validates with validateConfigObjectWithPlugins(cfg) and then serializes stampConfigVersion(cfg) (the original input), not validated.config. If the caller passes a runtime-merged object (with defaults already applied), those defaults will still be persisted even though applyModelDefaults was removed here. Consider serializing validated.config (or explicitly stripping defaults) to ensure the invariant “user config should only contain explicitly set values” actually holds.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/io.ts
Line: 489:496

Comment:
**Defaults still written**

`writeConfigFile` currently validates with `validateConfigObjectWithPlugins(cfg)` and then serializes `stampConfigVersion(cfg)` (the original input), not `validated.config`. If the caller passes a runtime-merged object (with defaults already applied), those defaults will still be persisted even though `applyModelDefaults` was removed here. Consider serializing `validated.config` (or explicitly stripping defaults) to ensure the invariant “user config should only contain explicitly set values” actually holds.


How can I resolve this? If you propose a fix, please make it concise.

@mcaxtr

mcaxtr commented Feb 8, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review! You raise a valid concern about writeConfigFile not stripping defaults from the input.

However, the scope of this PR is specifically issue #6070, which reports that openclaw config set and openclaw config unset corrupt the config file. My fix addresses this by:

  1. Introducing snapshot.resolved - the config after $include and ${ENV} resolution but before runtime defaults
  2. Updating config set and config unset to use snapshot.resolved instead of snapshot.config

This ensures the CLI commands pass the correct pre-defaults config to writeConfigFile.

You're correct that other call sites (like gmail-ops.ts passing validated.config) may have the same bug pattern. That's a broader issue affecting multiple code paths, and I'd argue it deserves a separate issue/PR to audit all writeConfigFile callers.

Making writeConfigFile itself strip defaults is tricky because:

  • validateConfigObjectWithPlugins returns validated.config with defaults, but we'd need the pre-default version
  • We'd need to either diff against defaults or track what was explicitly set vs inherited
  • This is a bigger architectural change that could have unintended side effects

Happy to discuss if you think the broader fix should be in this PR!

@mcaxtr

mcaxtr commented Feb 8, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@mcaxtr mcaxtr force-pushed the fix-6070-config-set-overwrite branch from 1cefb2b to d209559 Compare February 12, 2026 04:17
…et/unset

Fixes openclaw#6070

The config set/unset commands were using snapshot.config (which contains
runtime-merged defaults) instead of snapshot.parsed (the raw user config).
This caused runtime defaults like agents.defaults to leak into the written
config file when any value was set or unset.

Changed both set and unset commands to use structuredClone(snapshot.parsed)
to preserve only user-specified config values.
…s 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.
The newly added 'resolved' field contains secrets after ${ENV}
substitution. This commit ensures redactConfigSnapshot also redacts
the resolved field to prevent credential leaks in config.get responses.
@steipete steipete merged commit 7c25696 into openclaw:main Feb 13, 2026
23 of 24 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm test
  • Land commit: d933ae0
  • Merge commit: 7c25696

Thanks @mcaxtr!

@mcaxtr mcaxtr deleted the fix-6070-config-set-overwrite branch February 13, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: openclaw config set overwrites entire config when validation fails or config unreadable

2 participants