config set/patch Silently Discards Values Matching Schema Defaults
The `openclaw config set` and `config patch` commands report success (exit 0) while silently dropping writes for properties whose values match schema defaults, causing silent data loss and subsequent validation failures.
π Symptoms
CLI Reports Success Without Persisting Changes
When executing config set or config patch with values that match schema defaults, the CLI exits with code 0 and reports success, but the config file remains unchanged for those properties.
bash $ openclaw config set –batch-file batch.json Config overwrite: ~/.openclaw/openclaw.json (sha256 X -> Y, backup=…) Updated 2 config paths. Restart the gateway to apply.
$ echo $? 0
Config File Unchanged After Reported Success
bash $ grep -c ‘“dmPolicy”|“groupPolicy”’ ~/.openclaw/openclaw.json 0
$ diff <(jq -S . ~/.openclaw/openclaw.json.bak) <(jq -S . ~/.openclaw/openclaw.json)
only meta.lastTouchedAt differs
Dry-Run Validation Fails After “Successful” Write
The same fields that were “successfully” written now fail strict validation:
bash $ echo ‘{}’ | openclaw config patch –stdin –dry-run Error: Dry run failed: config schema validation failed.
- channels.telegram.dmPolicy: invalid config: must have required property ‘dmPolicy’
- channels.telegram.groupPolicy: invalid config: must have required property ‘groupPolicy’
Validator Divergence Between Commands
config validate passes lenient validation, but config patch --dry-run fails strict validation on the same file:
bash $ openclaw config validate Config valid: ~/.openclaw/openclaw.json # lenient β passes
$ echo ‘{}’ | openclaw config patch –stdin –dry-run Error: … must have required property ‘dmPolicy’ # strict β fails
Batch File Format Example
json [ {“path”: “channels.telegram.dmPolicy”, “value”: “pairing”}, {“path”: “channels.telegram.groupPolicy”, “value”: “allowlist”} ]
π§ Root Cause
1. Default Value Stripping in Config Writer
The underlying config set and config patch implementation performs value normalization during serialization. When a property’s value matches its schema-defined default, the writer omits it from the output file. This behavior is likely intentional for forward-compatibility (avoiding unnecessary churn in the config file), but it creates a silent data loss condition when:
- The property is marked
requiredin the schema - The property has a default value
- The user explicitly writes the default value
Code path (hypothesized):
typescript
// Schematic flow in config-writer.ts
function writeConfig(config: DeepPartial
2. Validator Divergence: Lenient vs Strict Modes
Two distinct validation paths exist in the codebase:
| Command | Validator | Behavior |
|---|---|---|
config validate | Lenient | Uses default filling; required fields pass if defaulted |
config patch --dry-run | Strict | Enforces required; fails if field is absent before merge |
This divergence causes operator confusion: a config passes validate, then immediately fails patch --dry-run with “missing required property” errors.
3. Schema Design Issue
The schema declares these fields as required but provides defaults:
json { “channels”: { “telegram”: { “dmPolicy”: { “type”: “string”, “default”: “pairing”, “required”: [“dmPolicy”] }, “groupPolicy”: { “type”: “string”, “default”: “allowlist”, “required”: [“groupPolicy”] } } } }
This creates an irreconcilable state: runtime requires the defaults to exist (for code), but the writer strips them (for storage cleanliness), and the strict validator enforces their presence before merge.
4. Merge Semantics in Patch Operation
config patch --stdin merges the incoming object with the existing config. When validation occurs:
- Existing config is loaded (defaults stripped)
- Incoming patch is merged
- Strict validator checks the merged result
- If merged result lacks
requiredfields, strict validation fails
The --dry-run path validates before applying, exposing the gap between what was written and what validation expects.
π οΈ Step-by-Step Fix
Workaround A: Direct File Edit (Recommended for Immediate Resolution)
Hand-edit ~/.openclaw/openclaw.json using jq or a text editor to insert the required properties:
bash
Backup before editing
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak
Add missing required fields with schema defaults
jq ‘.channels.telegram += {
“dmPolicy”: “pairing”,
“groupPolicy”: “allowlist”
}’ ~/.openclaw/openclaw.json > /tmp/openclaw-fixed.json
&& mv /tmp/openclaw-fixed.json ~/.openclaw/openclaw.json
Verify the fields are present
grep -c ‘“dmPolicy”|“groupPolicy”’ ~/.openclaw/openclaw.json
Expected output: 2
Before (openclaw.json):
json { “meta”: { “version”: “2026.5.7” }, “channels”: { “telegram”: {} } }
After (openclaw.json):
json { “meta”: { “version”: “2026.5.7” }, “channels”: { “telegram”: { “dmPolicy”: “pairing”, “groupPolicy”: “allowlist” } } }
Workaround B: Use Non-Default Values Temporarily
If the schema allows alternative enum values, write a non-default value first, then patch back to the default:
bash
First, write a non-default value
echo ‘[{“path”: “channels.telegram.dmPolicy”, “value”: “allowlist”}]’ \
/tmp/patch.json openclaw config set –batch-file /tmp/patch.json
Verify it persisted
grep ‘“dmPolicy”’ ~/.openclaw/openclaw.json
Now set the actual default
openclaw config set –batch-file batch.json
Verify persistence
grep ‘“dmPolicy”’ ~/.openclaw/openclaw.json
Note: This is fragile and may not work depending on whether the writer strips non-defaults followed by defaults in the same session.
Workaround C: Disable Default Stripping (If Source Modifiable)
If building from source, patch the config writer to preserve explicit values:
typescript // In src/config/writer.ts β BEFORE (problematic) function pruneDefaults(config: object, schema: object): object { return Object.fromEntries( Object.entries(config).filter(([k, v]) => v !== schema.defaults[k] // Strips matching values ) ); }
// In src/config/writer.ts β AFTER (fix)
function pruneDefaults(config: object, schema: object, explicitPaths: Set
Permanent Fix (For Core Team)
The root fix requires one of:
Option 1: Honor explicit writes regardless of default match
- Modify
config set/config patchto track explicitly-set paths - Preserve those paths in the serialized output even if values match defaults
Option 2: Warn on dropped writes
- When writes are pruned, emit warnings to stderr
- Return non-zero exit code or add
--warn-onlyflag
Option 3: Relax schema requirements
- Remove
requiredfrom fields with defaults - Document that defaults are always available at runtime
π§ͺ Verification
Step 1: Confirm Field Presence After Fix
bash $ grep -c ‘“dmPolicy”|“groupPolicy”’ ~/.openclaw/openclaw.json 2
Step 2: Run Lenient Validation
bash $ openclaw config validate Config valid: ~/.openclaw/openclaw.json
Exit code: 0
Step 3: Run Strict Dry-Run Patch Validation
bash $ echo ‘{}’ | openclaw config patch –stdin –dry-run
Expected: Should NOT fail with “must have required property” errors
If still fails, the fields may not have persisted correctly
Step 4: Verify Schema Integrity with Full Config Check
bash $ cat ~/.openclaw/openclaw.json | jq ' .channels.telegram.dmPolicy == “pairing” and .channels.telegram.groupPolicy == “allowlist” ' true
Step 5: Test Plugin Workflow End-to-End
bash $ openclaw plugins enable skill-workshop Plugin enabled. Restart gateway to activate.
$ openclaw config patch –file - «‘EOF’ {“channels”: {“telegram”: {“dmPolicy”: “pairing”}}} EOF Updated 1 config paths.
Verify no errors
$ openclaw config patch –dry-run –file - «‘EOF’ {“channels”: {“telegram”: {“dmPolicy”: “pairing”}}} EOF Dry run successful.
Exit Code Checklist
| Command | Expected Exit Code | Failure Indicator |
|---|---|---|
config validate | 0 | Non-zero or error output |
config patch --dry-run | 0 | Any validation error output |
config set --batch-file | 0 (with warning if drops occur) | Field missing after command |
β οΈ Common Pitfalls
Pitfall 1: Assuming --dry-run Guarantees Subsequent Success
config set --dry-run validates successfully but config set (without dry-run) may still strip defaults. Always verify persistence with grep or jq after applying.
Pitfall 2: Confusing Lenient and Strict Validators
Operators may run config validate (passes) and believe the config is fully valid, only to have config patch fail with strict validation. The validators serve different purposes and are not equivalent.
Pitfall 3: Batch File Ordering Dependencies
If a batch file contains multiple writes for the same parent object, the order may matter. Write all required fields in a single batch to avoid partial application issues.
Pitfall 4: Backup Restoration Overwrites Fix
If using openclaw config set --batch-file to restore a backup, defaults matching the schema defaults will be stripped again. Re-apply the direct edit workaround after any config set operation.
Pitfall 5: Docker Volume Persistence
When running OpenClaw in Docker, ~/.openclaw/openclaw.json resides in a volume. Edit the volume directly or use docker exec with jq:
bash
docker exec
/root/.openclaw/openclaw.json > /tmp/config.json
&& docker cp /tmp/config.json
Pitfall 6: Case-Sensitive Property Names
Property paths in batch files are case-sensitive. Verify exact casing against the schema:
bash $ openclaw schema get –path channels.telegram.dmPolicy
vs
$ cat batch.json # Ensure “dmPolicy” matches, not “dm_policy”
Pitfall 7: Restart Dependency
Some schema validation changes require a gateway restart to take effect. After applying the fix, restart the OpenClaw gateway:
bash openclaw gateway restart
π Related Errors
Error: must have required property '<path>'
Context: Strict validation failure during config patch --dry-run
Cause: Property exists in schema’s required array but is absent from config file after merge
Resolution: Add the property via direct file edit (see Workaround A)
Error: Config overwrite: ~/.openclaw/openclaw.json (sha256 X -> Y, backup=...) followed by unchanged file
Context: config set --batch-file reports success but file appears unchanged
Cause: All batch values matched schema defaults and were stripped
Resolution: Use direct file edit or non-default value workaround
Error: Dry run failed: config schema validation failed
Context: config patch --stdin --dry-run exits non-zero
Cause: Incoming patch causes strict validation failure (missing required fields post-merge)
Resolution: Ensure all required fields are present before applying patch
Error: Config valid: ~/.openclaw/openclaw.json but subsequent patch fails
Context: config validate passes but config patch fails on identical file
Cause: Validator divergence (lenient vs strict modes)
Resolution: Use config patch --dry-run for pre-apply validation instead of relying on config validate
Historical Issue: dm.policy β dmPolicy Migration Gap
Context: Doctor migration helper skips dmPolicy when legacy dm.policy field absent
Cause: Fresh configs have neither field; migration produces no output
Resolution: Manually add dmPolicy and groupPolicy as documented in this guide
Historical Issue: Plugin Enablement Blocked
Context: openclaw plugins enable <id> fails when required channel properties missing
Cause: Plugin enablement writes via config patch; strict validation fails on missing required fields
Resolution: Pre-populate required fields using direct file edit before enabling plugins