OpenClaw Config CLI Guide: config set, patch, --dry-run, and Safe openclaw.json Edits
The most reassuring line openclaw config set can print is Dry run successful. In one input mode it means almost nothing.
The pages that rank for openclaw.json are mostly tours of the file. Where it lives, a starter config, an annotated example, some security advice about dmPolicy. They are useful on day one. They say little about the act of changing the file once a gateway depends on it, and a couple of them repeat the claim that JSON5 comments survive every edit. They survive your editor. They do not survive the CLI. This page covers the four write commands, the checks each one actually runs, and what OpenClaw does with a write it refuses.
Find the File Before You Touch It
openclaw config file
openclaw config file --jsonThe default is ~/.openclaw/openclaw.json, and OPENCLAW_CONFIG_PATH overrides it. Run this first on any machine you did not set up yourself. Profiles and service wrappers move the file around more often than people expect, and an edit to the wrong copy validates perfectly while changing nothing.
One hard requirement: the path has to be a regular file. If you keep openclaw.json as a symlink into a dotfiles repo, CLI writes are unsupported. Point OPENCLAW_CONFIG_PATH at the real file instead.
Reading With config get
openclaw config get agents.defaults.model --json
openclaw config get 'agents.entries.main'
openclaw config schema > openclaw.schema.jsonconfig get reads a redacted snapshot, so secrets never print. That makes it safe to paste into a bug report, which is more than you can say for cat. Ask for a path you never set and it tells you the runtime default applies; ask for a path that does not exist and it points you at config schema. Quote any path with brackets in it. zsh will try to glob [0] and give you a baffling error that has nothing to do with OpenClaw.
config set and the Dry Run That Checks Nothing
Values parse as JSON5 when they can and fall back to a raw string when they cannot. That fallback is friendly at the prompt and dangerous in a script. Type 19001 and you get a number. Mistype a JSON object and you get a string that looks like one.
Now the dry run. In value mode (a plain value, no --strict-json), the dry run skips the full schema pass and the ordinary SecretRef resolvability scan. The docs say plainly that it can succeed for a value the real write would reject. When nothing applies, the CLI prints Dry run note: value mode does not run schema/resolvability checks, which is easy to scroll past at the bottom of a terminal. Add --strict-json and the schema check comes back:
# previews almost nothing
openclaw config set gateway.port 19001 --dry-run
# previews the full post-change config
openclaw config set gateway.port 19001 --strict-json --dry-run
openclaw config set gateway.port 19001 --strict-jsonMy rule is that --strict-json goes on every config set that lives in a file. Strings get their quotes ('"hybrid"', inside single quotes for the shell). It looks fussy. It turns your dry run into a real one.
Model edits get one extra check regardless of mode. Changing agents.defaults.model resolves the new primary and fallbacks through your catalogs, and an unknown reference is rejected without touching the file. openclaw models set is looser on purpose: it saves a model the local catalog cannot confirm and warns you. Pick whichever matches how sure you are of the model ID.
--merge, --replace, and the Maps That Fight Back
Assigning an object replaces whatever sat at that path. For most keys that is what you meant. For a handful of maps that collect your own entries over time, including agents.defaults.models, models.providers, plugins.entries and auth.profiles, OpenClaw refuses a replacement that would delete existing entries unless you pass --replace.
openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json --mergeUse --merge to add. I would not type --replace on one of those paths from memory. If you need the map to be exactly some value, write that value in a patch file where you can read the whole thing first.
config patch for Anything With Structure
A patch is a config-shaped JSON5 object. Objects merge recursively, arrays and scalars replace their target, and null deletes the path. Patch files are limited to 8 MiB and piped stdin to 1 MiB, neither of which you will hit by accident.
openclaw config patch --file ./discord.patch.json5 --dry-run
openclaw config patch --file ./discord.patch.json5
# remote host, no copying files around
ssh user@gateway-host 'openclaw config patch --stdin --dry-run' < ./discord.patch.json5The patch dry run runs the schema and SecretRef checks, so it does not share the value-mode blind spot. Arrays are the trap here. A patch that sets a list replaces the whole list; when you mean exactly that for an object, say so with --replace-path. For channel credentials, put SecretRefs in the patch rather than tokens, as the secrets guide explains.
The Gateway Has to Be Stopped
Easy to miss, because older posts do not mention it: the current reference says config set, config patch and config unset require the local gateway to be stopped, since they hold exclusive state ownership while publishing the file and its database metadata. Stop it through whatever owns the service, wait for it to exit, then write.
After a successful write the CLI prints one of three hints: Restart the gateway to apply., Change will apply without restarting the gateway., or No gateway restart needed. Read them as predictions. The docs call the hint an expected application, "not a receipt from a running Gateway." In the default hybrid reload mode most paths hot-apply; port, bind, auth mode, Tailscale and TLS under gateway.* still need a restart, and any path that matches no reload rule defaults to one.
A running gateway can still take edits two other ways. The Control UI Config tab at http://127.0.0.1:18789 renders a form from the live schema. Or you edit the file in your editor and let the watcher pick it up.
Where Refused Writes Go
Every OpenClaw-owned write validates the entire post-change config before anything hits disk. If the result fails the schema, or looks like a clobber (dropping the effective gateway.mode, or shrinking the file by more than half), the active file stays put and the rejected payload is saved beside it:
CONFIG="$(openclaw config file)"
ls -lt "$CONFIG".rejected.* 2>/dev/null | head
openclaw config validateThose files are worth opening. They contain exactly what you tried to write, which beats reconstructing it from shell history. Clean them up once you are done, because they hold whatever the write held.
Hand edits take a different path. The gateway treats a direct edit as untrusted until it validates; an invalid one gets skipped with config reload skipped (invalid config) in the log and the last good runtime keeps serving. Fine while it runs. On the next start, the same invalid file stops the gateway from booting at all, and only diagnostic commands work. The last-known-good copy exists, but startup will not restore it for you. Only openclaw doctor --fix does, as the gateway doctor guide walks through.
A watcher can also miss an edit entirely. A background reconciliation runs every five minutes to catch those, so "my change did nothing" sometimes means "wait four more minutes."
Comments Die on CLI Writes
OpenClaw-owned writes reserialize JSON5 as standard JSON. If your file has comments, the writer warns immediately before removing them, and then removes them. Years of // why this is 3 and not 5 notes go in one config set.
Choose. Either the file is CLI-managed and your notes live elsewhere (a README next to the backups works), or it is editor-managed and you stop running write commands against it. Mixing the two is how people lose context they cannot get back. Whichever you pick, snapshot the file first; the backup and migration guide has the routine.
Writes Your Automation Can Trust
If a script edits config on a schedule, give it a precondition:
openclaw config set gateway.port 19001 --strict-json --expect-current-json 18789
openclaw config set gateway.port 19001 --strict-json --expect-current-absentOn a mismatch nothing is saved, the command exits 1, and neither value is printed. These flags work only on a single config set, not with batch mode or --dry-run. And if a deployment system owns the file, set OPENCLAW_CONFIG_READONLY=1 in the host environment of both the gateway and the CLI. Putting it inside openclaw.json does nothing.
Common Failure Modes
Dry run passed, real write refused
You previewed in value mode. Rerun with --strict-json --dry-run, or move the change into a patch file.
Write refused on a models or plugins map
It is a protected path and your object would drop entries. Add --merge.
config unset exits 1
The key was never in the authored file. A value you see in config get may be a runtime default.
Edit saved, gateway behaves the same
Check the log for config reload skipped, then see whether the path needs a restart. Plugin code changes always do; the plugins guide lists which edits hot-apply.
Final Verdict
Use config set --strict-json for single values, a patch file with --dry-run for anything shaped like an object, and run openclaw config validate after both. Treat a value-mode dry run as a typo check. The rest of the safety net (whole-file validation before every OpenClaw-owned write, plus the rejected payload it leaves on disk) is already there and works well. It only catches what you let it see.
Ready to build?
Get the OpenClaw Starter Kit — config templates, 5 production-ready skills, deployment checklist. Go from zero to running in under an hour.
$14 $6.99
Get the Starter Kit →Also in the OpenClaw store
Get the free OpenClaw quickstart guide
Step-by-step setup. Plain English. No jargon.