#14179: fix(config): resolve $include directives before validation in setup and writeConfigFile
commands
stale
Cluster:
Config Fixes and Features
## Summary
- **Fix `setup` command breaking on modular `$include` configs**: `readConfigFileRaw()` parsed JSON5 without resolving `$include` directives, then passed the raw config to `writeConfigFile()` where Zod validation rejected `$include` as an unrecognized key.
- **Harden `writeConfigFile()` defensively**: Add `resolveConfigIncludes()` before validation so any caller that accidentally passes unresolved `$include` keys will still succeed.
## Problem
When using modular configs with per-key `$include` (e.g., `gateway: { $include: "./config/gateway.json5" }`), running `openclaw setup` fails with:
```
Error: Config validation failed: logging: Unrecognized key: "$include"
```
This happens because `setup.ts` used a private `readConfigFileRaw()` helper that only did `JSON5.parse()` without calling `resolveConfigIncludes()`. The `gateway` command works fine because it goes through `readConfigFileSnapshot()` which properly resolves includes.
## Changes
### `src/commands/setup.ts`
- Remove `readConfigFileRaw()` helper and unused `JSON5` import
- Use `io.readConfigFileSnapshot()` which properly resolves `$include`, env vars, and validates
### `src/config/io.ts`
- Add defensive `resolveConfigIncludes()` in `writeConfigFile()` before validation
- Falls back to the original config object if resolution fails (no-op for already-resolved configs)
- Ensures the written file also uses the resolved config
## Test plan
- [ ] Run `openclaw setup` with a modular `$include`-based config -- should succeed instead of throwing validation error
- [ ] Run `openclaw setup` with a standard (no `$include`) config -- should work as before
- [ ] Run `openclaw gateway` with a modular config -- should continue working
- [ ] Run `writeConfigFile()` with an already-resolved config -- defensive resolution is a no-op
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes a bug where `openclaw setup` failed on modular configs using `$include` directives by replacing the custom `readConfigFileRaw()` helper with the standard `readConfigFileSnapshot()` which properly resolves includes before validation. A defensive include resolution was also added to `writeConfigFile()` to catch any future callers that accidentally pass unresolved configs.
**Key changes:**
- `setup.ts`: Removed `readConfigFileRaw()` and switched to `io.readConfigFileSnapshot()` which handles `$include` resolution, env var substitution, and validation
- `io.ts`: Added defensive `resolveConfigIncludes()` call in `writeConfigFile()` before validation to handle any unresolved `$include` keys from callers
The fix correctly addresses the reported issue. The defensive resolution in `writeConfigFile()` provides a safety net, though the error handling could be more informative when include resolution fails.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with minor considerations for error handling improvements
- The core fix correctly addresses the reported bug by using the proper config reading method that resolves includes. The defensive resolution in writeConfigFile adds a safety layer. Score is 4 (not 5) because the error handling in the defensive resolution could potentially mask the root cause of include resolution failures, though this is a relatively minor concern that doesn't affect correctness in the common case.
- No files require special attention
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#5823: fix(config): exit cleanly on invalid config instead of high CPU loop
by gavinbmoore · 2026-02-01
76.7%
#19429: Fix/custom bind host validation
by frudas24 · 2026-02-17
75.7%
#19020: bugfix(gateway): Handle invalid model provider API config gracefully\…
by funkyjonx · 2026-02-17
75.3%
#13960: fix(ui): preserve structured config validation error details
by constansino · 2026-02-11
75.3%
#13849: fix(configure): preserve custom config keys across wizard sections
by mcaxtr · 2026-02-11
75.3%
#16301: fix: preserve custom config keys during configure wizard
by superlowburn · 2026-02-14
75.1%
#11602: fix(config): skip stale legacy config files when openclaw.json exists
by akoscz · 2026-02-08
75.1%
#11663: fix: prevent config page header overlap with settings form
by shogunsea · 2026-02-08
74.9%
#17916: [ fix ] : correct config directory path during onboarding
by Dijo-404 · 2026-02-16
74.7%
#19129: fix(config): block destructive config writes instead of only loggin...
by pierreeurope · 2026-02-17
74.7%