Skip to main content

Config writes

Every path that mutates openclaw.json goes through one writer: writeConfigFile in the core config I/O layer. The gateway RPC methods (config.set, config.patch, config.apply), the CLI, the chat /config command, and plugins all funnel through it. This page documents what that writer does, which gateway methods write config, and when a change is hot-reloaded versus requiring a restart.

Write paths

Hand-editing the file in a text editor is also supported — the gateway watches the file and applies the reload policy described in Hot reload versus restart.

Gateway RPC methods

config.patch is the safe default for targeted changes: only the paths present in the patch are touched, everything else is preserved as it exists on disk (including keys the caller never saw). config.apply replaces the whole document and is meant for intentional full replacements.

Optimistic concurrency (baseHash)

Key: baseHash (request parameter) — no default; required by every write method when a config file already exists. The flow:
  1. The client calls config.get and receives the snapshot plus its hash.
  2. The client sends that hash as baseHash with config.set / config.patch / config.apply.
  3. The gateway re-reads the file. If the hash no longer matches, the write is refused with config changed since last load; re-run config.get and retry.
This prevents two writers from clobbering each other’s changes. If you see the error, re-read the config and retry — someone (or something) wrote between your read and your write.

Validation and secret redaction

Before anything touches disk:
  • Schema validation. The merged document is validated against the full config schema, including schemas contributed by loaded plugins and channel plugins. An invalid write is refused before the file is modified, with the offending path and issue in the error.
  • Secret redaction. config.get returns the config with secret values (API keys, tokens) replaced by redaction markers. On write, unchanged redacted values are restored from the file on disk — a client that read, modified one unrelated key, and wrote back cannot accidentally blank your secrets, and the agent never sees secret values in the first place.
  • Environment variable references. If the file uses ${VAR} references (for example "${ANTHROPIC_API_KEY}"), the writer restores the reference instead of persisting the resolved value, unless the write actually changed that value.

Top-level key removal guard

Default: refusals on; cannot be disabled globally. A write that would drop a top-level key that exists on disk (for example losing the whole channels or agents block) is refused with error code CONFIG_WRITE_REFUSED:
To remove a top-level key intentionally, the caller must name it in allowTopLevelKeyRemoval. The CLI openclaw config unset and the chat /config unset command do this automatically for the path you name. The agent’s gateway tool never sends allowTopLevelKeyRemoval, so the assistant cannot silently delete top-level config blocks. Advisory conditions are logged but never block: a greater-than-50% size shrink, a missing meta block, or removal of gateway.mode.

Write mechanics: atomicity, backups, audit

Every write follows the same sequence:
  1. Serialize the validated document as pretty-printed JSON with a stamped config version.
  2. Write to a temporary file in the same directory with mode 0600.
  3. Rotate backups: the previous file is copied to openclaw.json.bak, and the existing ring shifts — openclaw.json.bak.1 through openclaw.json.bak.4 (5 backups total, permissions hardened to 0600, orphan .bak.* files pruned).
  4. Atomically rename the temp file over openclaw.json (with a copy fallback on Windows).
  5. Append an audit record to ~/.openclaw/logs/config-audit.jsonl.
The audit record captures timestamps, previous and next hashes and sizes, process identity, changed-path count, and the outcome (rename, copy-fallback, failed, or refused). To answer “who changed the config and when”, read that file:
Gateway-scope writes (config.patch, config.apply) additionally log a control-plane line with the acting client, device id, client IP, and the changed paths.

Restart gate

Key: gateway.restartPolicy — default "drain"; values "drain" | "force" | "cancel". config.patch and config.apply restart the gateway after writing (via SIGUSR1, with restart coalescing and an optional restartDelayMs). Before that, the restart gate checks for active runs (in-flight agent turns and embedded runs): The gate check happens before any file write, so a confirmation round-trip never leaves a half-applied state on disk. After the restart completes, a restart sentinel (written before the restart) delivers the writer’s note back to the session that requested the change — this is how the assistant reports “done” after it restarts itself. This change requires a gateway restart: config.patch and config.apply perform it automatically. config.set does not — see below.

Hot reload versus restart

Key: gateway.reload.mode — default "hybrid"; values "off" | "restart" | "hot" | "hybrid". Companion key: gateway.reload.debounceMs — default 300.
The file watcher (chokidar) watches openclaw.json and reacts to external edits — including writes made via config.set, the CLI, /config, or your text editor. When the file changes, the gateway debounces, re-reads, and validates. An invalid file is never applied: the reload is skipped with a warning and the previous config keeps running. Which strategy applies:
  • "hybrid" (default) — the gateway classifies the changed paths and hot-reloads what it can, restarts when required.
  • "hot" — hot-reload only. Changes that would require a restart are ignored with a warning.
  • "restart" — restart the gateway for every config change.
  • "off" — never react to config changes; nothing is applied until the next manual restart.
Classification of changed paths (hybrid mode):
config.patch and config.apply bypass this classification: they always schedule a restart after writing. The reload policy governs external edits and config.set writes. If you want a patch-style change without the restart, use config.set with the full document, or accept the restart.
Restart commands when you need them manually (Linux: systemctl --user restart openclaw-gateway; macOS: wednesdayai gateway restart --deep).

Troubleshooting