OpenClaw Doctor: Health Checks, Migrations, and Repairs
This page covers the openclaw doctor command for health checks, config migrations, and repair steps. It is intended for users needing to fix outdated configurations or state.
Read this when
- Adding or modifying doctor migrations
- Introducing breaking config changes
openclaw doctor serves as OpenClaw's repair and migration utility. It resolves outdated configuration and state, performs health assessments, and offers concrete remediation guidance.
Quick start
openclaw doctor
Headless and automation modes
--yes
openclaw doctor --yes
Proceed without asking for confirmation, covering restart, service, and sandbox fixes where relevant.
--fix
openclaw doctor --fix
Execute suggested repairs without prompting; --repair works as an alternative.
--lint
openclaw doctor --lint
openclaw doctor --lint --json
Execute structured health evaluations suited for CI or preflight workflows. This operates in read-only mode: no prompts, repairs, migrations, restarts, or state modifications occur.
--fix --force
openclaw doctor --fix --force
Also apply more intensive repairs, which will overwrite custom supervisor configurations.
--non-interactive
openclaw doctor --non-interactive
Operate without prompts, limiting actions to safe migrations such as config normalization and on-disk state relocation. Restart, service, and sandbox operations requiring human approval are omitted. Legacy state migrations continue to run automatically when found.
--deep
openclaw doctor --deep
Inspect system services for additional gateway installations across launchd, systemd, and schtasks.
To preview modifications prior to applying them, open the configuration file first:
cat ~/.openclaw/openclaw.json
Read-only lint mode
openclaw doctor --lint is the automation-oriented counterpart to openclaw doctor --fix. Both rely on the same Doctor rule registry, yet their selection and execution of rules differ:
| Mode | Prompts | Writes config/state | Output | Use it for |
|---|---|---|---|---|
openclaw doctor | yes | no | friendly health report | a human checking status |
openclaw doctor --json | no | no | JSON advisory report | machine-readable operator checks |
openclaw doctor --fix | sometimes | yes, with repair policy | friendly repair log | applying approved repairs |
openclaw doctor --lint | no | no | structured findings | CI, preflight, and review gates |
By default, doctor --lint adopts a broadly safe automation profile, running checks that are static, local, and suitable for CI or preflight output. Opt-in checks that are advisory, environment-sensitive, dependent on live services, account or workspace inventory, or historical cleanup are excluded. Use doctor --lint --all to run the complete registered lint audit including those opt-in checks, or --only <id> to focus on a specific check.
doctor --fix bypasses the lint default profile and rejects --all. Instead, it follows Doctor's ordered repair sequence: modern health checks may supply an optional repair() implementation, while older areas continue using the legacy Doctor repair flow. Certain lint findings are intentionally diagnostic only, so a check listed in --lint --all does not guarantee that --fix will modify that area. The design keeps detect() (which reports findings) separate from repair() (which reports changes, diffs, and side effects), leaving room for a future doctor --fix --dry-run without converting lint checks into mutation planners.
Several built-in checks are disabled by default internally, keeping them available to --all, --only, and Doctor repair flows while excluding them from the default doctor --lint automation profile. Finding severity is still reported per finding as info, warning, or error; default selection does not indicate severity.
openclaw doctor --lint
openclaw doctor --lint --severity-min warning
openclaw doctor --lint --json
openclaw doctor --lint --all
openclaw doctor --lint --only core/doctor/gateway-config --json
Fields in the JSON output:
ok: indicates whether any finding reached the chosen severity thresholdchecksRun/checksSkipped: totals (excluded by profile,--only, or--skip)findings: structured diagnostics containingcheckId,severity,message, and optionallypath,line,column,ocPath,source,target,requirement,fixHint
Exit codes:
| Code | Meaning |
|---|---|
0 | no findings at or above the selected threshold |
1 | one or more findings met the selected threshold |
2 | command/runtime failure before findings could be emitted |
These exit codes, which rely on thresholds, belong to explicit --lint mode, whether --json is present or not. Using plain openclaw doctor --json keeps Doctor's usual advisory exit 0 after the payload is generated; automated consumers should look at ok and findings. Errors that occur before any output is produced still result in a non-zero status.
Flags:
--severity-min info|warning|error(defaults towarning): determines both the printed content and what triggers a non-zero exit.--all: executes all registered lint checks, including those opt-in ones that the default automation set leaves out.--only <id>(can be repeated): runs only the specified check id(s); an id that is not recognized gets reported as an error finding.--skip <id>(can be repeated): removes a check from the run while the rest stay active.--severity-min,--all,--only, and--skipall depend on--lint. Plain--jsonis permitted for an advisory machine-readable report;--fixturns it down unless another machine mode is responsible for the output.
What it does (summary)
Health, UI, and updates
- Optional pre-flight update for git-based installs, only in interactive sessions.
- UI protocol freshness check, which rebuilds the Control UI when the protocol schema is newer.
- Health check followed by a restart prompt.
- Skill and plugin notes that only appear when there are problems; healthy inventory remains in
openclaw skills checkandopenclaw plugins list.
Config and migrations
- Config normalization for legacy value shapes.
- Safe migration of legacy default HTTPS Tailscale Serve routes from a LAN-bound Gateway to managed loopback ingress. Retired named-Service config gets removed while managed ingress is off, until the operator picks a device route; custom external routes get manual guidance.
- Talk config migration from legacy flat
talk.*fields intotalk.providerplustalk.providers.<provider>. - Browser migration checks for legacy Chrome extension configs, owned native-bootstrap registration drift, and Chrome MCP readiness.
- OpenCode provider override warnings (
models.providers.opencode/opencode-zen/opencode-go). - Legacy OpenAI Codex provider/profile migration (
openai-codex→openai) and shadowing warnings for stalemodels.providers.openai-codex. - OAuth TLS prerequisites check for OpenAI Codex OAuth profiles.
- Plugin/tool allowlist warnings when
plugins.allowis restrictive but tool policy still asks for wildcard or plugin-owned tools. - Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
- Retired QMD memory config and derived workspace cleanup; see Migrating from QMD.
- Legacy plugin manifest contract key migration (
speechProviders,realtimeTranscriptionProviders,realtimeVoiceProviders,mediaUnderstandingProviders,imageGenerationProviders,videoGenerationProviders,webFetchProviders,webSearchProviders→contracts). - Legacy cron store migration (
jobId,schedule.cron, top-level delivery/payload fields, payloadprovider,notify: truewebhook fallback jobs). - Legacy workspace
TOOLS.mdmigration into the## Toolssection ofAGENTS.md, with the original archived under the state directory before removal. - Codex CLI runtime pin repair (
agentRuntime.id: "codex-cli"→"codex") acrossagents.defaults,agents.entries.*, andmodels.providers.*(including per-model entries). - Stale plugin config cleanup when plugins are enabled; when
plugins.enabled=false, stale plugin references are preserved as inert containment config.
State and integrity
- Session lock file inspection and stale lock cleanup.
- Session transcript repair for duplicated prompt-rewrite branches created by affected 2026.4.24 builds.
- Wedged main-session and subagent restart-recovery tombstone detection. Doctor reports the blocked sessions and only repairs stale aborted flags that conflict with an existing tombstone; it does not re-enable automatic recovery.
- State integrity and permissions checks (sessions, transcripts, state dir).
- Config file permission checks (chmod 600) when running locally.
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
Gateway, services, and supervisors
- Repairs sandbox images when sandboxing is turned on.
- Moves legacy services and detects extra gateways.
- Migrates legacy Matrix channel state when running in
--fixor--repairmode. - Performs gateway runtime checks, such as a service that is installed but not running, or a cached launchd label.
- Reports channel status warnings based on probes from the active gateway.
- Channel-specific permission checks are located under
openclaw channels capabilities; for instance, Discord voice channel permissions get audited throughopenclaw channels capabilities --channel discord --target channel:<channel-id>. - Checks WhatsApp responsiveness to catch degraded Gateway event-loop health while local TUI clients are still active;
--fixterminates only verified local TUI clients. - Fixes Codex routes for legacy
openai-codex/*model references across primary models, fallbacks, image and video generation models, heartbeat, subagent, and compaction overrides, hooks, channel model overrides, and session route pins;--fixconverts them toopenai/*, migratesopenai-codex:*auth profiles and their order toopenai:*, clears stale session and whole-agent runtime pins, and lets the corrected effective route decide Codex compatibility. - Audits supervisor config (launchd, systemd, schtasks) with optional repair.
- Cleans up embedded proxy environment variables for gateway services that captured shell
HTTP_PROXY,HTTPS_PROXY, orNO_PROXYvalues during installation or updates. - Runs gateway runtime checks for unsupported legacy Bun services and version-manager paths.
- Diagnoses gateway port collisions, defaulting to
18789.
Auth, security, and pairing
- Flags security warnings for open DM policies.
- Checks gateway auth for local token mode, offering token generation when no token source exists, but never overwriting token SecretRef configurations.
- Detects device pairing issues, including pending first-time pair requests, pending role or scope upgrades, stale local device-token cache drift, and paired-record auth drift.
Workspace and shell
- Verifies the systemd linger setting on Linux.
- Checks workspace bootstrap file sizes, warning about truncation or near-limit context files.
- Assesses skills readiness for the default agent, reporting allowed skills that lack required bins, env, config, or OS prerequisites;
--fixcan turn off unavailable skills inskills.entries. - Checks shell completion status and auto-installs or upgrades as needed.
- Verifies memory search embedding provider readiness, whether a local model or a remote API key is used.
- Runs source install checks for pnpm workspace mismatches, missing UI assets, and missing tsx binaries.
- Writes updated configuration and wizard metadata.
Dreams UI backfill and reset
The Control UI Dreams scene provides Backfill, Reset, and Clear Grounded actions for the grounded dreaming workflow. These rely on gateway doctor-style RPC methods but are not part of openclaw doctor CLI repair or migration.
| Action | What it does |
|---|---|
| Backfill | Scans historical memory/YYYY-MM-DD.md files in the active workspace, runs the grounded REM diary pass, and writes reversible backfill entries into DREAMS.md. |
| Reset | Removes only the marked backfill diary entries from DREAMS.md. |
| Clear Grounded | Removes only staged grounded-only short-term entries from historical replay that have not accumulated live recall or daily support yet. |
None of these edit MEMORY.md, run full doctor migrations, or stage grounded candidates into the live short-term promotion store on their own. To feed grounded historical replay into the normal deep promotion lane, use the CLI flow instead:
openclaw memory rem-backfill --path ./memory --stage-short-term
That stages grounded durable candidates into the short-term dreaming store while DREAMS.md stays the review surface.
Detailed behavior and rationale
0. Optional update (git installs)
If this is a git checkout and doctor is running interactively, it offers to update (fetch/rebase/build) before running doctor.
1. Config normalization
Doctor normalizes legacy value shapes into the current schema. Current Talk speech config is talk.provider + talk.providers.<provider>, with realtime voice config under talk.realtime.*. Doctor rewrites old talk.voiceId / talk.voiceAliases / talk.modelId / talk.outputFormat / talk.apiKey shapes into the provider map, and rewrites legacy top-level realtime selectors (talk.mode, talk.transport, talk.brain, talk.model, talk.voice) into talk.realtime.
Doctor also warns when plugins.allow is non-empty and tool policy uses wildcard or plugin-owned tool entries. tools.allow: ["*"] only matches tools from plugins that actually load; it does not bypass the exclusive plugin allowlist.
2. Legacy config key migrations
When the config contains a deprecated key with an active migration, other commands refuse to run and ask you to run openclaw doctor. Doctor explains which legacy keys were found, shows the migration it applied, and rewrites ~/.openclaw/openclaw.json with the updated schema. Gateway startup refuses legacy config formats and asks you to run openclaw doctor --fix; it does not rewrite openclaw.json on startup. Cron job store migrations are also handled by openclaw doctor --fix.
Note
Doctor only carries automatic migrations for roughly two months after a key is retired. Older legacy keys (for example the original
routing.queue,routing.bindings,routing.agents/defaultAgentId,routing.transcribeAudio, top-levelagent.*, or top-levelidentityfrom the pre-multi-agent config shape) no longer have a migration path; config using them now fails validation instead of being rewritten. Fix those keys by hand against the current config reference before doctor can proceed.
Active migrations:
| Legacy key | Current key |
|---|---|
routing.allowFrom | channels.whatsapp.allowFrom |
routing.groupChat.requireMention | channels.whatsapp/telegram/imessage.groups."*".requireMention |
routing.groupChat.historyLimit | messages.groupChat.historyLimit |
routing.groupChat.mentionPatterns | messages.groupChat.mentionPatterns |
channels.telegram.requireMention | channels.telegram.groups."*".requireMention |
channels.webchat, gateway.webchat | removed (WebChat is retired) |
channels.feishu.accounts.<accountId>.botName | channels.feishu.accounts.<accountId>.name |
session.threadBindings.ttlHours, channels.<id>.threadBindings.ttlHours (and per-account) | ...threadBindings.idleHours |
legacy talk.voiceId/talk.voiceAliases/talk.modelId/talk.outputFormat/talk.apiKey | talk.provider + talk.providers.<provider> |
legacy top-level realtime Talk selectors (talk.mode/talk.transport/talk.brain/talk.model/talk.voice) | talk.realtime |
messages.tts | top-level tts |
messages.tts.<provider> (openai/elevenlabs/microsoft/edge) | tts.providers.<provider> |
messages.tts.provider: "edge" / messages.tts.providers.edge | tts.provider: "microsoft" / tts.providers.microsoft |
tools.exec.security + tools.exec.ask | tools.exec.mode |
session.idleMinutes | session.reset.idleMinutes |
messages.responsePrefix with explicit channel blocks | copied to configured channel/account responsePrefix; global fallback retained for implicit/custom channels |
web.enabled | channels.whatsapp.enabled |
meta.lastTouchedAt, hook installs, cron store, bundled discovery, global TTS prefs path | shared SQLite state |
TTS speaker fields voice/voiceName/voiceId | speakerVoice/speakerVoiceId |
channels.<id>.tts.<provider> / channels.<id>.accounts.<accountId>.tts.<provider> (all channels except Discord) | ...tts.providers.<provider> |
channels.<id>.voice.tts.<provider> / channels.<id>.accounts.<accountId>.voice.tts.<provider> (every channel, Discord included) | ...voice.tts.providers.<provider> |
plugins.entries.voice-call.config.tts.<provider> (openai/elevenlabs/microsoft/edge) | plugins.entries.voice-call.config.tts.providers.<provider> |
plugins.entries.voice-call.config.tts.provider: "edge" / ...tts.providers.edge | provider: "microsoft" / ...tts.providers.microsoft |
plugins.entries.voice-call.config.provider: "log" | "mock" |
plugins.entries.voice-call.config.twilio.from | plugins.entries.voice-call.config.fromNumber |
plugins.entries.voice-call.config.streaming.sttProvider | plugins.entries.voice-call.config.streaming.provider |
plugins.entries.voice-call.config.streaming.openaiApiKey/sttModel/silenceDurationMs/vadThreshold | plugins.entries.voice-call.config.streaming.providers.openai.* |
models.providers.*.api: "openai" | "openai-completions" (during gateway startup, providers with a api set to an unknown or future enum value are skipped rather than causing a hard failure) |
browser.ssrfPolicy.allowPrivateNetwork | browser.ssrfPolicy.dangerouslyAllowPrivateNetwork |
browser.profiles.*.driver: "extension" paired with an outdated cdpUrl | driver kept, outdated relay URL dropped |
browser.relayBindHost | eliminated (legacy Chrome extension relay setting) |
mcp.servers.*.type (CLI-native aliases) | mcp.servers.*.transport |
mcp.servers.*.disabled | opposite of mcp.servers.*.enabled |
MCP timeout aliases connectTimeout/connect_timeout/timeout | connectionTimeoutMs/requestTimeoutMs |
| MCP snake-case server fields | MCP server fields in camelCase |
tools.media.image/audio/video.models | capability-tagged tools.media.models |
tools.media.asyncCompletion | removed |
tools.message.allowCrossContextSend | tools.message.crossContext |
media model deepgram options | providerOptions.deepgram |
talk.realtime.voice, Discord realtime voice | speakerVoice |
agents.defaults.pdfMaxBytesMb | agents.defaults.pdfMaxMb |
tools.exec.timeoutSec | tools.exec.timeoutSeconds |
browser.ssrfPolicy.hostnameAllowlist | wildcard-aware browser.ssrfPolicy.allowedHostnames |
sandbox browser enableNoVnc | noVncEnabled |
root media | attachments |
channel/account heartbeat visibility blocks | heartbeatVisibility |
channels.slack.identity | channels.slack.postAs |
root audit | logging.audit |
gateway.nodes.skills.enabled | gateway.nodes.allowSkills |
gateway.nodes.allowCommands/denyCommands | gateway.nodes.commands.allow/deny |
| generation model defaults | agents.defaults.mediaModels.{image,video,music} |
| retired final-layout tuning knobs | built-in default behavior |
channels.whatsapp.messagePrefix and legacy messages.messagePrefix | channels.whatsapp.responsePrefix |
channels.whatsapp.ackReaction | global messages.ackReaction and ackReactionScope where translatable |
cron.failureDestination | destination fields on cron.failureAlert |
gateway.controlUi.chatMessageMaxWidth, presentation-only ui.prefs keys | removed (text scale, chat width, and live sidebar activity are browser-local) |
agents.list | keyed agents.entries |
top-level defaultModel | agents.defaults.model |
messages.messagePrefix | channels.whatsapp.responsePrefix |
session.maintenance.pruneDays, session.resetByType.dm | session.maintenance.pruneAfter, session.resetByType.direct |
top-level tui | removed (the TUI footer uses the compact default) |
plugins.entries.codex.config.codexDynamicToolsProfile | removed (Codex app-server always keeps Codex-native workspace tools native) |
commands.modelsWrite | removed (/models add is deprecated) |
agents.defaults/list[].silentReplyRewrite, surfaces.*.silentReplyRewrite | removed (exact NO_REPLY is no longer rewritten to visible fallback text) |
agents.defaults/list[].systemPromptOverride | removed (OpenClaw owns the generated system prompt) |
agents.defaults/list[].embeddedPi | embeddedAgent |
agents.defaults/list[].sandbox.perSession | sandbox.scope |
agents.defaults.llm | removed (use models.providers.<id>.timeoutSeconds for slow model/provider timeouts, kept below the agent/run timeout ceiling) |
top-level memorySearch, agents.defaults.memorySearch | memory.search |
agents.entries.*.memorySearch | agents.entries.*.memory.search |
memorySearch.provider: "auto" | "openai" |
memorySearch.store.path (any level) | removed (memory indexes live in each agent database) |
top-level heartbeat | agents.defaults.heartbeat / channels.defaults.heartbeat |
plugins.openai-codex policy ids | plugins.openai |
tools.web.x_search.apiKey | plugins.entries.xai.config.webSearch.apiKey |
session.maintenance.rotateBytes, session.parentForkMaxTokens | removed (deprecated) |
| Runtime and channel tuning knobs retired in 2026.7 | removed (built-in production defaults apply) |
Note
On every config load, the Voice Call plugin itself normalizes the
plugins.entries.voice-call.config.*rows above, notopenclaw doctor. A startup warning pointing atopenclaw doctor --fixis also logged by the plugin, yet doctor does not currently rewriteopenclaw.jsonfor these keys; the plugin's own normalization is what applies the change at runtime.
Guidance on account defaults for multi-account channels:
- When two or more
channels.<channel>.accountsentries are set up withoutchannels.<channel>.defaultAccountoraccounts.default, doctor issues a warning that fallback routing might select an unexpected account. - If
channels.<channel>.defaultAccountpoints to an account ID that doesn't exist, doctor warns and provides a list of configured account IDs.
2b. OpenCode provider overrides
If you've manually added models.providers.opencode, opencode-zen, or opencode-go while the corresponding official external plugin is installed and active, it takes precedence over the plugin-provided catalog. This can push models to the wrong API or zero out costs. Doctor alerts you so you can drop the override and bring back per-model API routing plus costs. Without the matching plugin, the entry stays valid as a standalone custom provider.
2c. Browser migration and Chrome MCP readiness
When an extension-driver profile still carries a retired relay cdpUrl, doctor strips that URL while keeping driver: "extension"; the current extension relay owns its endpoint. The retired browser.relayBindHost setting is also removed by doctor.
Doctor warns while browser.extensionRelay.allowLegacyAuth is enabled. Upgrade paired Chrome extensions and external CDP clients to Browser Relay Authentication v2, then set the flag to false. V2 clients do not downgrade to legacy authentication.
When a stable Chrome extension copy and owned native-host registration already exist, doctor reports registration drift. openclaw doctor --fix may repair that owned registration, but it never installs the host for every OpenClaw user and never overwrites a foreign same-name manifest or launcher. For initial setup, run openclaw browser extension install first, then add the official Chrome Web Store extension. The unpacked stable path is a development fallback.
Doctor also audits the host-local Chrome MCP path when you use defaultProfile: "user" or a configured existing-session profile:
- verifies that Google Chrome is installed on the same host for default auto-connect profiles
- checks the detected Chrome version and warns when it is below Chrome 144
- reminds you to enable remote debugging in the browser inspect page (for example
chrome://inspect/#remote-debugging,brave://inspect/#remote-debugging, oredge://inspect/#remote-debugging)
Doctor cannot enable the Chrome-side setting for you. Host-local Chrome MCP still requires a Chromium-based browser 144+ on the gateway/node host, running locally, with remote debugging enabled and the first attach consent prompt approved in the browser.
Readiness here only covers local attach prerequisites. Existing-session keeps the current Chrome MCP route limits; advanced routes like responsebody, PDF export, download interception, and batch actions still require a managed browser or raw CDP profile. This check does not apply to Docker, sandbox, remote-browser, or other headless flows, which continue to use raw CDP.
2d. OAuth TLS prerequisites
When an OpenAI Codex OAuth profile is configured, doctor probes the OpenAI authorization endpoint to verify that the local Node/OpenSSL TLS stack can validate the certificate chain. If the probe fails with a certificate error (for example UNABLE_TO_GET_ISSUER_CERT_LOCALLY, expired cert, or self-signed cert), doctor prints platform-specific fix guidance. On macOS with a Homebrew Node, the fix is usually brew postinstall ca-certificates. With --deep, the probe runs even if the gateway is healthy.
2e. Codex OAuth provider overrides
If you previously added legacy OpenAI transport settings under models.providers.openai-codex, they can shadow the built-in Codex OAuth provider path. Doctor warns when it sees those old transport settings alongside Codex OAuth so you can remove or rewrite the stale transport override and restore current routing behavior. Custom proxies and header-only overrides remain supported and do not trigger this warning, but those authored request routes are not eligible for implicit Codex selection.
2f. Codex route repair
Doctor checks for legacy openai-codex/* model refs. Native Codex harness routing uses canonical openai/* model refs, but the prefix alone never selects Codex. With runtime policy unset or auto, only an exact official HTTPS Platform Responses or ChatGPT Responses route with no authored request override is eligible. See OpenAI implicit agent runtime.
In --fix / --repair mode, doctor rewrites affected default-agent and per-agent refs, including primary models, fallbacks, image/video generation models, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale persisted session route state:
openai-codex/gpt-*is transformed intoopenai/gpt-*.- For repaired agent model references, Codex intent is redirected to provider/model-scoped
agentRuntime.id: "codex"entries. - Whole-agent runtime configuration that has gone stale, along with persisted session runtime pins, gets removed since runtime selection now operates at the provider/model level.
- The existing provider/model runtime policy stays intact, unless the repaired legacy model reference requires Codex routing to maintain the previous auth path.
- Model fallback lists are kept, but their legacy entries are rewritten; per-model settings that were copied are shifted from the legacy key to the canonical
openai/*key. - Across every discovered agent session store, persisted session
modelProvider/providerOverride,model/modelOverride, fallback notices, and auth-profile pins are all repaired. - Doctor independently fixes stale
agentRuntime.id: "codex-cli"pins (a distinct legacy runtime id) to"codex"acrossagents.defaults,agents.entries.*, andmodels.providers.*model entries. /codex ...denotes "control or bind a native Codex conversation from chat."/acp ...orruntime: "acp"indicates "use the external ACP/acpx adapter."
2g. Session route cleanup
After you relocate configured models or runtime away from a plugin-owned route such as Codex, Doctor also examines discovered agent session stores for stale auto-created route state.
When their owning route is no longer configured, openclaw doctor --fix can remove auto-created stale state, including modelOverrideSource: "auto" model pins, runtime model metadata, pinned harness ids, CLI session bindings, and auto auth-profile overrides. Explicit user or legacy session model choices are flagged for manual review and remain untouched; use /model ..., /new, or reset the session to switch them when that route is no longer intended.
3. Legacy state migrations (disk layout)
Doctor can convert older on-disk layouts into the current structure:
- Sessions store + transcripts: from
~/.openclaw/sessions/to~/.openclaw/agents/<agentId>/sessions/ - Agent dir: from
~/.openclaw/agent/to~/.openclaw/agents/<agentId>/agent/ - WhatsApp auth state (Baileys): from legacy
~/.openclaw/credentials/*.json(exceptoauth.json) to~/.openclaw/credentials/whatsapp/<accountId>/...(default account id:default) - Signed device identity: from
~/.openclaw/identity/device.jsoninto theprimarydevice_identitiesrow instate/openclaw.sqlite; Gateway startup also performs this verified import for valid legacy identities, while Doctor retains repair authority for invalid canonical rows; the separate device-auth file is left untouched
These migrations are best-effort and idempotent; doctor emits warnings when it leaves any legacy folders behind as backups. The Gateway/CLI also auto-migrates the legacy sessions + agent dir on startup so history/auth/models land in the per-agent path without a manual doctor run. WhatsApp auth is intentionally only migrated via openclaw doctor. Talk provider/provider-map normalization compares by structural equality, so key-order-only diffs no longer trigger repeat no-op doctor --fix changes.
When an explicit roster no longer contains main, OpenClaw migrates durable agent:main:* SQLite rows only if the replacement owner is unambiguous: the sole roster member or the configured upgrade owner in agents.defaults.sessionStore.agentId. The explicit owner works for both per-agent and fixed session stores; fixed-store runtime ownership remains scoped to that physical store. Conflicting canonical or alias rows are preserved during startup and reported with a Doctor hint. openclaw doctor --fix first imports any legacy JSON session store, then keeps the winning canonical claim and renames each losing claim to agent:<owner>:legacy-main-conflict-<n> in its original database. Quarantine changes only the key; the entry and full transcript remain available for inspection or archival.
3a. Legacy plugin manifest migrations
Doctor scans all installed plugin manifests for deprecated top-level capability keys (speechProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, mediaUnderstandingProviders, imageGenerationProviders, videoGenerationProviders, webFetchProviders, webSearchProviders). When found, it offers to move them into the contracts object and rewrite the manifest file in-place. This migration is idempotent; if contracts already has the same values, the legacy key is removed without duplicating data.
3b. Legacy cron store migrations
Doctor also checks the legacy cron job store (~/.openclaw/cron/jobs.json) for old job shapes before importing canonical rows into SQLite.
Current cron cleanups include:
jobId→idschedule.cron→schedule.expr- top-level payload fields (
message,model,thinking, ...) →payload - top-level delivery fields (
deliver,channel,to,provider, ...) →delivery - payload
providerdelivery aliases → explicitdelivery.channel - legacy
notify: truewebhook fallback jobs → explicit webhook delivery from the retired rawcron.webhookvalue when valid; announce jobs keep their chat delivery and getdelivery.completionDestination. Doctor then removes the old config key. Without a usable legacy webhook, the inert top-levelnotifymarker is removed for no-target jobs (existing delivery, including announce, is preserved) since runtime delivery never reads it.
The Gateway also sanitizes malformed cron rows at load time so valid jobs keep running. Malformed rows are quarantined in the shared SQLite state database in the same transaction that removes them from active scheduling; doctor reports those records and imports any jobs-quarantine.json sidecars left by older releases.
Gateway startup normalizes the runtime projection and ignores the top-level notify marker, but leaves persisted cron state for doctor repair. Doctor removes inert markers for jobs with no migration target (delivery.mode none/absent, an unusable legacy webhook target, or existing announce/chat delivery), leaving existing delivery untouched, so repeated doctor --fix runs no longer re-warn about the same job.
On Linux, doctor also warns when the user's crontab still invokes legacy ~/.openclaw/bin/ensure-whatsapp.sh. That host-local script is not maintained by current OpenClaw and can write false Gateway inactive messages to ~/.openclaw/logs/whatsapp-health.log when cron cannot reach the systemd user bus. Remove the stale crontab entry with crontab -e; use openclaw channels status --probe, openclaw doctor, and openclaw gateway status for current health checks.
3c. Session lock cleanup
Doctor scans every agent session directory for stale write-lock files left behind when a session exited abnormally. For each lock file found it reports: the path, PID, whether the PID is still alive, lock age, and whether it is considered stale (dead PID, malformed owner metadata, older than 30 minutes, or a live PID proven to belong to a non-OpenClaw process). In --fix / --repair mode it removes locks with dead, orphaned, recycled, malformed-old, or non-OpenClaw owners automatically. Old locks still owned by a live OpenClaw process are reported but left in place so doctor does not cut off an active transcript writer.
3d. Session transcript branch repair
Doctor scans agent session JSONL files for the duplicated branch shape created by the 2026.4.24 prompt transcript rewrite bug: an abandoned user turn with OpenClaw internal runtime context plus an active sibling containing the same visible user prompt. In --fix / --repair mode, doctor backs up each affected file next to the original and rewrites the transcript to the active branch so gateway history and memory readers no longer see duplicate turns.
4. State integrity checks (session persistence, routing, and safety)
The state directory is the operational brainstem. If it vanishes, you lose sessions, credentials, logs, and config unless you have backups elsewhere.
Doctor checks:
- State dir missing: warns about catastrophic state loss, prompts to recreate the directory, and reminds you that it cannot recover missing data.
- State dir permissions: verifies writability; offers to repair permissions (and emits a
chownhint when owner/group mismatch is detected). - macOS cloud-synced state dir: warns when state resolves under iCloud Drive (
~/Library/Mobile Documents/com~apple~CloudDocs/...) or~/Library/CloudStorage/..., because sync-backed paths can cause slower I/O and lock/sync races. - Linux SD or eMMC state dir: warns when state resolves to an
mmcblk*mount source, because SD/eMMC-backed random I/O can be slower and wear faster under session and credential writes. - Linux volatile state dir: warns when state resolves to
tmpfsorramfs, because sessions, credentials, config, and SQLite state (with WAL/journal sidecars) disappear on reboot. Dockeroverlaymounts are intentionally not flagged because their writable layers persist across host reboots while the container remains. - Session dirs missing:
sessions/and the session store directory are required to persist history and avoidENOENTcrashes. - Transcript mismatch: warns when recent session entries have missing transcript files.
- Main session "1-line JSONL": flags when the main transcript has only one line (history is not accumulating).
- Multiple state dirs: warns when multiple
~/.openclawfolders exist across home directories, or whenOPENCLAW_STATE_DIRpoints elsewhere (history can split between installs). - Remote mode reminder: if
gateway.mode=remote, doctor reminds you to run it on the remote host (the state lives there). - Config file permissions: warns if
~/.openclaw/openclaw.jsonis group/world readable and offers to tighten to600.
5. Model auth health (OAuth expiry)
Doctor inspects OAuth profiles in the auth store, warns when tokens are expiring/expired, and can refresh them when safe. If the Anthropic OAuth/token profile is stale, it suggests an Anthropic API key or the Anthropic setup-token path. Refresh prompts only appear when running interactively (TTY); --non-interactive skips refresh attempts.
When an OAuth refresh fails permanently (for example refresh_token_reused, invalid_grant, or a provider telling you to sign in again), doctor reports that re-auth is required and prints the exact openclaw models auth login --provider ... command to run.
Doctor also reports auth profiles that are temporarily unusable due to short cooldowns (rate limits/timeouts/auth failures) or longer disables (billing/credit failures).
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are repaired only by doctor. Run openclaw doctor --fix once from an interactive terminal to migrate Keychain-backed legacy tokens inline into auth-profiles.json; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them as canonical OpenAI OAuth profiles.
6. Hooks model validation
If hooks.gmail.model is set, doctor validates the model reference against the catalog and allowlist and warns when it will not resolve or is disallowed.
7. Sandbox image repair
With sandboxing turned on, doctor verifies Docker images and, when the current image is absent, proposes building one or switching to legacy names.
7b. Plugin install cleanup
In openclaw doctor --fix / openclaw doctor --repair mode, doctor clears out legacy plugin dependency staging state left by OpenClaw: stale generated dependency roots, old install-stage directories, package-local debris from earlier bundled-plugin dependency repair code, and orphaned or recovered managed npm copies of bundled @openclaw/* plugins that can obscure the current bundled manifest. Additionally, doctor relinks the host openclaw package into managed npm plugins that declare peerDependencies.openclaw, ensuring package-local runtime imports like openclaw/plugin-sdk/* continue to resolve after updates or npm repairs.
When config references downloadable plugins that the local plugin registry cannot locate (material plugins.entries, configured channel/provider/search settings, configured agent runtimes), doctor can reinstall them. During package updates, doctor skips reinstalling plugin packages while the core package is being swapped; if a configured plugin still needs recovery after the update, run openclaw doctor --fix again. Apart from the container image startup exception below, gateway startup and config reload do not trigger package repair; plugin installs remain explicit doctor/install/update tasks.
Containerized gateway startup has a limited upgrade exception: when openclaw gateway run launches on a new OpenClaw version, it performs safe state migrations and the existing post-core plugin convergence before readiness, then logs a per-version checkpoint. This startup pass can remove stale bundled-plugin records, fix local plugin links, reinstall configured plugin packages when the convergence path demands it, and verify active plugin payloads. If startup cannot repair safely, run the same image once with openclaw doctor --fix against the same mounted state/config before restarting the container normally.
8. Gateway service migrations and cleanup hints
Doctor identifies legacy gateway services (launchd/systemd/schtasks) and offers to remove them, installing the OpenClaw service on the current gateway port. It can also search for extra gateway-like services and output cleanup suggestions. Profile-named OpenClaw gateway services are treated as first-class and are not marked as "extra."
On Linux, when the user-level gateway service is absent but a system-level OpenClaw gateway service exists, doctor does not automatically install a second user-level service. Check with openclaw gateway status --deep or openclaw doctor --deep, then remove the duplicate or set OPENCLAW_SERVICE_REPAIR_POLICY=external when a system supervisor manages the gateway lifecycle.
8b. Startup Matrix migration
For a Matrix channel account with a pending or actionable legacy state migration, doctor (in --fix / --repair mode) takes a pre-migration snapshot and then executes the best-effort migration steps: legacy Matrix state migration and legacy encrypted-state preparation. Both steps are non-fatal; errors are logged and startup proceeds. In read-only mode (openclaw doctor without --fix) this check is skipped entirely.
8c. Device pairing and auth drift
Doctor examines device-pairing state during the standard health pass, reporting:
- pending first-time pairing requests
- pending role or scope upgrades for already-paired devices
- public-key mismatch repairs where the device id still matches but the device identity no longer matches the approved record
- paired records missing an active token for an approved role
- paired tokens whose scopes drift outside the approved pairing baseline
- local cached device-token entries for the current machine that predate a gateway-side token rotation or carry stale scope metadata
Doctor does not auto-approve pair requests or auto-rotate device tokens. It prints the exact next steps:
- inspect pending requests with
openclaw devices list - approve the exact request with
openclaw devices approve <requestId> - rotate a fresh token with
openclaw devices rotate --device <deviceId> --role <role> - remove and re-approve a stale record with
openclaw devices remove <deviceId>
This separates first-time pairing from pending role/scope upgrades and from stale token/device-identity drift, addressing the common "already paired but still getting pairing required" gap.
9. Security warnings
Doctor emits a Security note only when it finds a warning, such as a provider open to DMs without an allowlist or a dangerously configured policy. Use openclaw security audit for the full security inventory.
10. systemd linger (Linux)
If running as a systemd user service, doctor ensures lingering is enabled so the gateway stays alive after logout.
11. Workspace status (skills, plugins, and TaskFlows)
Doctor prints problems and actions for the default agent, not healthy-state inventory:
- Skills: lists allowed but unusable skill names; use
openclaw skills checkfor requirement details and full counts. - Plugins: reports only errored plugin IDs; use
openclaw plugins listfor loaded, imported, disabled, and bundle-plugin inventory. - Plugin compatibility warnings: flags plugins that have compatibility issues with the current runtime.
- Plugin diagnostics: surfaces any load-time warnings or errors emitted by the plugin registry.
- TaskFlow recovery: surfaces suspicious managed TaskFlows that need manual inspection or cancellation.
- Claude CLI: reports only binary, authentication, profile, workspace, or project-directory problems; healthy probe details are omitted.
11b. Bootstrap file size
Doctor checks whether workspace bootstrap files (for example AGENTS.md, CLAUDE.md, or other injected context files) are near or over the configured character budget. It reports per-file raw vs. injected character counts, truncation percentage, truncation cause (max/file or max/total), and total injected characters as a fraction of the total budget. When files are truncated or near the limit, doctor prints tips for tuning agents.defaults.bootstrapMaxChars and agents.defaults.bootstrapTotalMaxChars.
11c. Shell completion
Doctor checks whether tab completion is installed for the current shell (zsh, bash, fish, or PowerShell):
- If the shell profile uses a slow dynamic completion pattern (
source <(openclaw completion ...)), doctor upgrades it to the faster cached file variant. - If completion is configured in the profile but the cache file is missing, doctor regenerates the cache automatically.
- If no completion is configured at all, doctor prompts to install it (interactive mode only; skipped with
--non-interactive).
Run openclaw completion --write-state to regenerate the cache manually.
11d. Stale channel plugin cleanup
When openclaw doctor --fix removes a missing channel plugin, it also removes the dangling channel-scoped config that referenced that plugin: channels.<id> entries, heartbeat targets that named the channel, and agents.*.models["<channel>/*"] overrides. This prevents Gateway boot loops where the channel runtime is gone but config still asks the gateway to bind to it.
12. Gateway auth checks (local token)
Doctor checks local gateway token auth readiness.
- If token mode needs a token and no token source exists, doctor offers to generate one.
- If
gateway.auth.tokenis SecretRef-managed but unavailable, doctor warns and does not overwrite it with plaintext. openclaw doctor --generate-gateway-tokenforces generation only when no token SecretRef is configured.
12b. Read-only SecretRef-aware repairs
Some repair flows need to inspect configured credentials without weakening runtime fail-fast behavior.
openclaw doctor --fixuses the same read-only SecretRef summary model as status-family commands for targeted config repairs.- Example: Telegram
allowFrom/groupAllowFrom@usernamerepair tries to use configured bot credentials when available. - If the Telegram bot token is configured via SecretRef but unavailable in the current command path, doctor reports that the credential is configured-but-unavailable and skips auto-resolution instead of crashing or misreporting the token as missing.
13. Gateway health check + restart
Doctor runs a health check and offers to restart the gateway when it looks unhealthy.
13b. Memory search readiness
Doctor checks whether the configured memory search embedding provider is ready for the default agent. The behavior depends on the configured provider:
- Explicit local provider: looks for a model file stored locally or a URL that points to a recognized downloadable model. When neither is found, it recommends switching to a remote provider.
- Explicit remote provider (
openai,voyage, etc.): confirms that an API key exists in the environment or the auth store. If it is absent, it prints concrete steps to resolve the issue. - Legacy auto provider: interprets
memorySearch.provider: "auto"as OpenAI, checks that OpenAI is ready, anddoctor --fixrewrites it toprovider: "openai".
When a cached gateway probe result exists (the gateway was healthy during the check), doctor cross-references that result against the CLI-visible config and flags any differences. The default path does not trigger a fresh embedding ping; run the deep memory status command if you need a live provider check.
To verify embedding readiness at runtime, use openclaw memory status --deep.
14. Channel status warnings
When the gateway is healthy, doctor runs a channel status probe and surfaces warnings along with suggested fixes.
15. Supervisor config audit + repair
Doctor examines the installed supervisor config (launchd/systemd/schtasks) for missing or outdated defaults, such as systemd network-online dependencies and restart delay. If a mismatch is found, it recommends an update and can rewrite the service file or task to the current defaults.
Notes:
openclaw doctorasks for confirmation before rewriting supervisor config.openclaw doctor --yesaccepts the default repair prompts.openclaw doctor --fixapplies recommended fixes without prompting (--repairis an alias).openclaw doctor --fix --forceoverwrites custom supervisor configs.OPENCLAW_SERVICE_REPAIR_POLICY=externalkeeps doctor read-only for the gateway service lifecycle. It still reports service health and performs non-service repairs, but skips service install, start, restart, bootstrap, supervisor config rewrites, and legacy service cleanup because an external supervisor manages that lifecycle.- On macOS, a same-label system LaunchDaemon blocks user LaunchAgent install, start, restart, and bootstrap repair. Doctor reports the system owner and halts service recovery;
--forcedoes not bypass this ownership boundary. See Existing system LaunchDaemons. - On Linux, doctor does not rewrite command or entrypoint metadata while the matching systemd gateway unit is active. It also ignores inactive non-legacy extra gateway-like units during the duplicate-service scan so companion service files do not generate cleanup noise.
- If token auth requires a token and
gateway.auth.tokenis managed by SecretRef, doctor service install or repair validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata. - Doctor detects managed
.envor SecretRef-backed service environment values that older LaunchAgent, systemd, or Windows Scheduled Task installs embedded inline and rewrites the service metadata so those values load from the runtime source instead of the supervisor definition. - Doctor detects when the service command still pins an old
--portaftergateway.portchanges and rewrites the service metadata to the current port. - If token auth requires a token and the configured token SecretRef is unresolved, doctor blocks the install or repair path with actionable guidance.
- If both
gateway.auth.tokenandgateway.auth.passwordare configured andgateway.auth.modeis unset, doctor blocks install or repair until mode is set explicitly. - For Linux user-systemd units, doctor token drift checks include both
Environment=andEnvironmentFile=sources when comparing service auth metadata. - Doctor service repairs refuse to rewrite, stop, or restart a gateway service from an older OpenClaw binary when the config was last written by a newer version. See Gateway troubleshooting.
- A full rewrite can always be forced via
openclaw gateway install --force.
16. Gateway runtime + port diagnostics
Doctor inspects the service runtime (PID, last exit status) and warns when the service is installed but not running. It also checks for port collisions on the gateway port (default 18789) and reports likely causes, such as the gateway already running or an SSH tunnel.
17. Gateway runtime best practices
Doctor warns when the gateway service runs on Bun or a version-managed Node path (nvm, fnm, volta, asdf, etc.). Bun cannot open OpenClaw's node:sqlite state store, so repairs migrate legacy Bun services to Node. Version-manager paths can break after upgrades because the service does not load your shell init. Doctor offers to migrate to a system Node install when available (Homebrew, apt, choco).
Newly installed or repaired macOS LaunchAgents use a canonical system PATH (/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin) instead of copying the interactive shell PATH, so Homebrew-managed system binaries stay available while Volta, asdf, fnm, pnpm, and other version-manager directories do not change which Node child processes resolve. Linux services still keep explicit environment roots (NVM_DIR, FNM_DIR, VOLTA_HOME, ASDF_DATA_DIR, BUN_INSTALL, PNPM_HOME) and stable user-bin directories, but guessed version-manager fallback directories are only written to the service PATH when those directories exist on disk.
18. Config write + wizard metadata
Doctor persists any config changes and stamps wizard metadata to record the doctor run.
19. Workspace tips (backup + memory system)
Doctor suggests a workspace memory system when missing and prints a backup tip if the workspace is not already under git.
See /concepts/agent-workspace for a full guide to workspace structure and git backup (recommended private GitHub or GitLab).