← Back to PRs

#14179: fix(config): resolve $include directives before validation in setup and writeConfigFile

by ken2190 open 2026-02-11 16:35 View on GitHub →
commands stale
## 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