What openclaw doctor --fix actually does
Doctor without flags checks; --fix stops the Gateway, migrates, and restarts once, and since 2.0 it's the only path to the SQLite session store. The modes, the migrations, and the TTY trap.
openclaw doctor with no flags is a health check: it prints findings and changes nothing. openclaw doctor --fix is the repair and migration tool: it stops the Gateway, applies every migration your install needs, and restarts it once. Since OpenClaw 2.0 (2026.8.1) it is also the only path from an older install to the new SQLite session store, so any 2026.7.x install with history has to run it exactly once. Run it with a real terminal attached, ssh -t or docker run -it rather than a plain script: on 2026.8.1 a release-day bug made headless runs silently skip the 2.0 migrations (fixed in the releases since), and on any version the output is what tells you the migration happened. And back up first; the command rewrites state by design.
The three modes
Check: openclaw doctor runs advisory checks and exits. It prints findings in human or machine-readable form and applies nothing. This is the one to run after an update when you want to know whether anything needs attention: read the ok flag and the findings list.
Fix: openclaw doctor --fix (and its sibling --repair) applies supported repairs, prompting before the interactive ones. It is careful about the Gateway around it: it stops the matching managed Gateway before inspecting, runs its repairs with other processes excluded, verifies readiness, then restarts once, and it “preserves the service definition and does not start a service that was already stopped.”
Lint: --lint is stricter than both: “always read-only, never prompts, never applies safe migrations.” Use it in CI or anywhere you want a verdict with a guarantee that nothing moved.
--force escalates to aggressive config and state repairs. --non-interactive skips prompts for headless runs, and the docs say safe automatic migrations still apply in that mode. As of the 2.0 release week, treat that last sentence with suspicion; the section below explains why.
What --fix actually migrates in 2.0
The 2.0 doctor owns a long inventory of one-time migrations. The ones most installs will actually feel:
The session store. Legacy sessions.json and JSONL transcripts become SQLite, by default at
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqliteThis is the blocking one: “Gateway and local CLI startup do not import, restore, or rewrite legacy session JSON/JSONL files. When startup finds a legacy session store, it refuses readiness and prints a doctor --fix command” instead of serving empty history. There is no path around it; doctor is “the only owner for persistent file-to-SQLite migrations.”
The models map. A legacy agents.defaults.models map that was doubling as a restriction list becomes a modelPolicy allowlist, without silently opening access: a migrated install keeps the restrictions it had, while a fresh 2.0 install has none.
Model routes. Legacy codex/* and openai-codex/* refs are rewritten to openai/* everywhere they can hide: primaries, fallbacks, allowlists, heartbeat and subagent overrides, hooks, channel overrides, cron payloads, and stale session route pins.
And the rest. The exec-approvals format (old generated entries removed, manual allowlist rules left alone), the cron store (legacy JSON imported into SQLite and the files archived), auth profiles relocated into the shared state/openclaw.sqlite, MCP OAuth credentials imported, provider packages that moved out of the core bundle reinstalled, stale plugin config cleaned, and a stack of smaller renames. Each migration verifies its result before archiving what it replaced.
The TTY trap, and its fix
The docs draw the line like this: “Interactive prompts (keychain/OAuth fixes, etc.) only run when stdin is a TTY,” while safe migrations are supposed to apply in headless runs too. Issue #134036, filed on 2.0’s release day, reported that on 2026.8.1 the second half did not hold: run over SSH without a terminal, doctor --fix, --fix --force and --repair --non-interactive all re-printed the advice to run doctor and skipped the 2.0 state migrations, while the identical command under ssh -tt migrated immediately. The fix merged the same day (pull request #134078, August 31) and the issue is closed; 2026.8.2 and 2026.9.1 both shipped after it. If the version you are installing is 2026.8.1 itself, the bug is in it: install the current release instead.
A server is still exactly where doctor tends to run without a terminal: deploy scripts, cron, docker compose exec -T. A terminal costs nothing, so the habit stays:
# over SSH
ssh -t your-server "openclaw doctor --fix"
# in Docker, against the same state mount the Gateway uses
docker run -it --rm -v <openclaw-state>:/home/node/.openclaw <image> openclaw doctor --fixThen read the output rather than trusting the exit: a run that migrated says so, with the database path and the before and after schema versions. A run that only repeats “run openclaw doctor --fix” has done nothing. The wider 2.0 picture, including the second release-day bug this paired with, is in our OpenClaw 2.0 write-up.
What it does to your config, and the backups it keeps
Any config write, a --fix repair included, rotates a backup to ~/.openclaw/openclaw.json.bak with a numbered .bak.1 to .bak.4 ring behind it, so the last five shapes of your config are always on disk. --fix also drops unknown config keys that fail schema validation, listing each removal, and it skips that pruning while an update is in progress, so partially written upgrade state is not stripped before its migration finishes. If openclaw.json cannot be parsed at all and no last-known-good copy can be recovered, doctor leaves the file unchanged and exits with an error instead of writing a partial replacement.
Session migrations get their own safety net: each import writes a manifest under session-sqlite-migration-runs/ in the state directory before moving transcript files into the archive, and recovery works from those manifests.
The session-sqlite toolkit
Doctor also carries a set of --session-sqlite subcommands for working with the new store directly. inspect reads counts without importing, dry-run parses legacy files and reports what an import would do without writing, validate compares legacy sources against SQLite and exits non-zero on a mismatch, import performs the migration, compact reclaims space after large deletes, recover restores the latest failed migration run, and restore brings archived transcripts back without deleting SQLite data, the one you need if you ever downgrade to a file-backed version. Add --session-sqlite-all-agents to cover every agent store, not just the default. The destructive modes hold the same ownership lock as Gateway startup; inspect, dry-run and validate stay read-only.
When to run it, and the safe sequence
Three moments call for doctor: after an update, as a sanity check; when the Gateway refuses to start and its error names doctor, which after a version bump usually means the session store is waiting on its migration (with a restart policy that refusal looks like a crash loop); and when something is visibly off and you want findings before you guess. Container installs get one grace: on a new version, openclaw gateway run performs safe repairs before reporting ready, and only tells you to run the image once with doctor when it cannot finish safely.
The sequence that respects what the command does:
openclaw gateway stop
openclaw backup create --verify
openclaw doctor --fix # with a real terminal attached
openclaw gateway startThen send the agent a real message through a real channel and confirm history came back. A repair tool that stops your Gateway, rewrites state, and restarts it deserves a backup in front of it and a verification behind it, every time.