May 02, 2026 β€’ Version: 2026.5.7

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 required in 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): void { const normalized = applySchemaDefaults(config); const serialized = serializeConfig(normalized); // Writer strips values === schema defaults const pruned = pruneDefaults(serialized, schema); fs.writeFileSync(pruned); // User’s explicit value never reaches disk }

2. Validator Divergence: Lenient vs Strict Modes

Two distinct validation paths exist in the codebase:

CommandValidatorBehavior
config validateLenientUses default filling; required fields pass if defaulted
config patch --dry-runStrictEnforces 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:

  1. Existing config is loaded (defaults stripped)
  2. Incoming patch is merged
  3. Strict validator checks the merged result
  4. If merged result lacks required fields, 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

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): object { return Object.fromEntries( Object.entries(config).filter(([k, v]) => // Preserve if explicitly set, even if matches default explicitPaths.has(k) || v !== schema.defaults[k] ) ); }

Permanent Fix (For Core Team)

The root fix requires one of:

Option 1: Honor explicit writes regardless of default match

  • Modify config set/config patch to 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-only flag

Option 3: Relax schema requirements

  • Remove required from 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

CommandExpected Exit CodeFailure Indicator
config validate0Non-zero or error output
config patch --dry-run0Any validation error output
config set --batch-file0 (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 jq ‘.channels.telegram.dmPolicy = “pairing”’
/root/.openclaw/openclaw.json > /tmp/config.json
&& docker cp /tmp/config.json :/root/.openclaw/openclaw.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

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

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.