Skip to main content

openclaw doctor

Health checks and quick fixes for the gateway, channels, plugins, skills, model routing, local state, and config migrations. Use it whenever something is not behaving as expected and you want one command to explain what is wrong. When run for a managed Gateway, Doctor compares active official plugins with the OpenClaw package referenced by the installed service. This check still works when the Gateway is stopped or unreachable. When an older Gateway is still running, Doctor reports its version separately from the post-restart version. If the service package cannot be identified, Doctor reports restart readiness as unknown instead of treating the plugin set as compatible. When Gateway status reports degraded SecretRef owners, doctor prints a Secret runtime degradation warning with every cold or stale owner, affected config path, redacted reason, and the openclaw secrets reload retry command. When channel ingress events are dead-lettered, doctor names each affected channel account and points to openclaw channels dead-letters list for inspection and recovery. When the Gateway has exporter health facts, doctor reports the latest trusted per-signal state and transport under Telemetry exporters. The summary is redacted and does not include endpoint values, headers, certificates, payloads, or raw errors. Related:

Postures

Doctor supports these postures: Use openclaw doctor --json when an operator or script wants the advisory Doctor report as JSON. It exits successfully after producing a report; inspect ok and findings for health state. Use explicit openclaw doctor --lint --json when CI should exit nonzero for findings at the selected severity threshold. Prefer --fix when a human operator wants Doctor to edit config or state. For read-only diagnosis, use --lint or bare --json. Ordinary doctor, including doctor --non-interactive, can copy legacy config and migrate state even without --fix. --non-interactive suppresses prompts, not writes. After an exec-approval format upgrade, Doctor reports older generated approvals that are no longer active because they were not tied to a working directory. openclaw doctor --fix removes those inactive generated entries and leaves manual allowlist rules unchanged. Rerun affected workflows and choose Always allow here to renew trust for the intended directory. The normal openclaw update finalization runs this safe repair automatically. Explicit repair stops the matching managed Gateway before inspecting plugins or mutable state, excludes other processes during repair, verifies readiness, and restarts the same service once. It preserves the service definition and does not activate a service confirmed offline before maintenance. A loaded, enabled macOS job between respawns is not offline: Doctor stops it before repair and resumes it afterward. Run repair from a shell outside the Gateway process tree. For externally supervised or unmatched installations, stop and start the Gateway through its owning supervisor. This maintenance window also applies when repair ultimately finds no changes. Runs without --fix, --repair, or --yes do not enter maintenance. Custom state directories remain runtime-only and do not adopt a native service. --force alone does not select repair mode: openclaw doctor --force remains guided and still requires interactive consent before an eligible service rewrite. With --fix, --repair, or --yes, it allows aggressive config/state repairs but preserves the installed service definition. Force does not bypass service ownership, write-access, or interactive-only confirmation requirements.
doctor --fix follows explicitly configured workspace and store paths, including paths outside OPENCLAW_STATE_DIR. Setting OPENCLAW_STATE_DIR and OPENCLAW_CONFIG_PATH to a copy does not redirect those paths. Before rehearsing repairs on copied state, copy the external workspaces and stores too, then rewrite their paths in the copied config to point to the copies. Otherwise, Doctor can modify the originals.
When an updater supplies an explicit Gateway activation policy, Doctor leaves stop and restart ownership with that updater. The native manager must confirm the service is already offline before repair. If openclaw update --no-restart reaches Doctor while that service is running, repair fails without stopping or restarting it; stop the service through its owner, then retry the update. If service inspection is unavailable or an unmatched service can still run, Doctor refuses maintenance before changing config or state. Inspect it with openclaw gateway status --deep, restore service-manager access, and stop the service through its owner. Once the native manager confirms it is offline, Doctor can repair its selected state without changing or starting that service. If migration or config repair cannot finish, Doctor leaves the stopped service stopped and reports an incomplete repair. Resolve the reported blocker, rerun openclaw doctor --fix, then start the service through its owner.

Gateway service recovery

Run openclaw gateway status --deep to inspect the installed service and its runtime before choosing a recovery action. Use openclaw gateway install for a missing service, openclaw gateway start for an installed service that is not loaded, or openclaw gateway install --force from the intended installation to replace its service definition. Externally managed services still belong to their supervisor. For legacy services or conflicting systemd scopes, run openclaw doctor interactively to review the findings and confirm supported cleanup. Cleanup reports what it removed or skipped; it does not guarantee a replacement service will be installed. Explicit repair maintenance skips this separate cleanup flow.

Remote Gateway recovery

With gateway.mode: "remote", a failed Gateway health check does not trigger local service install, start, restart, or bootstrap prompts. Check the remote URL, credentials, and SSH tunnel or network connection. If the Gateway itself needs recovery, run service commands on the host that runs it. A loopback remote URL can be an SSH tunnel; it does not make the Gateway a local service. See Remote access for connection checks. Other Doctor config and state checks still follow the selected posture above.

Control UI assets

For source installs, Doctor can build missing Control UI assets or rebuild stale assets after protocol changes. Its manual build command includes the detected checkout path (pnpm --dir <checkout> ui:build), so you can run the displayed command from another directory. Use the complete command, including its quoted path, rather than running pnpm ui:build in an unrelated project. Packaged installs without UI sources receive reinstall guidance instead of a source-build command. Doctor does not download a source checkout to repair a packaged installation.

Examples

For channel-specific permissions, use the channel probes instead of doctor:
channels capabilities reports the bot’s effective permissions for a specific channel target. channels status --probe audits all configured channels and voice auto-join targets.

Options

--severity-min, --all, --only, and --skip are only accepted together with --lint. Bare --json uses the default read-only lint check selection but keeps Doctor’s advisory exit behavior. Both read-only postures reject --repair, --fix, --force, --yes, and --generate-gateway-token. Explicit --lint also rejects --session-sqlite modes and their selectors, including --github-issue. Other machine modes can still use --json for their own output.

Lint mode

Bare openclaw doctor --json is read-only and non-interactive: no prompts, repairs, or config/state rewrites. It emits the same default findings as lint mode, but exits 0 after a report is produced so output formatting does not change ordinary Doctor’s advisory success contract. Read the payload’s ok and findings fields to determine health. Explicit openclaw doctor --lint is the deployment-preflight posture. Add --json for machine-readable output without changing lint’s threshold-based exit code.
The managed local embedding setup check is a scoped, non-mutating pre-cutover gate for existing semantic indexes. It is opt-in through --only or --all, so plain doctor --lint behavior stays unchanged. It reports missing llama.cpp setup and the interactive models auth login remediation without claiming full Gateway readiness, starting services, downloading models, or changing config. Human output is compact:
JSON output is the scripting surface:
Explicit lint exit codes: --severity-min controls both which findings print and the exit threshold: openclaw doctor --lint --severity-min error can print nothing and exit 0 even when lower-severity info/warning findings exist. Bare openclaw doctor --json exits 0 once it emits a findings payload, including when ok is false. Argument errors or runtime failures before a payload can be produced remain nonzero. --all controls which checks are selected before severity filtering. The default lint run excludes checks that are deep, historical, or more likely to surface repairable legacy residue; use --all for the complete inventory. --only <id> is the most precise selector and can run any registered check by id. core/doctor/local-audio-acceleration reports the auto-selected local STT command, separate capable/requested/observed backend evidence, and fallback order without loading a speech model. It emits an informational finding, so include --severity-min info to display it.

Structured health checks

Modern doctor checks use a small split contract:
detect() powers doctor --lint. repair() is optional and only runs under doctor --fix / doctor --repair. Checks that have not migrated to this shape still use the legacy doctor contribution flow. Repair contexts can carry dryRun/diff requests; repair results can return structured diffs (config/file edits) and effects (service, process, package, state, or other side effects), so converted checks can grow toward doctor --fix --dry-run without moving mutation planning into detect(). repair() reports status: "repaired" | "skipped" | "failed" (omitted status means repaired). When repair returns skipped or failed, doctor reports the reason and skips validation for that check. After a successful repair, doctor re-runs detect() scoped to the repaired findings; if the finding is still present, doctor reports a repair warning instead of treating the change as complete. A finding includes: Modernized core doctor checks stay attached to the ordered doctor contribution that owns their human doctor / doctor --fix behavior. The shared structured health registry is the extension point: bundled and plugin-backed checks run after core doctor checks once their owning package registers them in the active command path. openclaw/plugin-sdk/health exposes the same contract for plugin authors.

Check selection

--only and --skip accept full check ids and may be repeated. An unregistered --only id emits a core/doctor/lint-selection error finding; valid selected checks still run. Use checksRun/checksSkipped in the output to confirm a focused gate selects the checks you expect. To check model credentials, run openclaw doctor --lint --only core/doctor/auth-profiles --json. This opt-in check inspects shared credentials and each configured agent’s local auth store, including fleets without a default agent. Shared credential problems are reported once; agent-specific cooldowns remain attributed to their local store.

Post-upgrade mode

openclaw doctor --post-upgrade runs plugin compatibility probes for chaining after a build or upgrade. Findings go to stdout; exit code is 1 if any finding has level: "error". Add --json for a machine-readable envelope ({ probesRun, findings }), suitable for CI, the community fork-upgrade skill, and other post-upgrade smoke tooling. If the installed plugin index is missing or malformed, JSON mode still emits the envelope with a plugin.index_unavailable error finding. The probes also warn with plugin.version_drift when an enabled official plugin in the installed index belongs to a different release cohort than the upgraded OpenClaw CLI. Follow the reported plugin update command, then restart the Gateway. Exact npm pins receive an update command only after the registry confirms that target exists. Independently versioned community plugins and disabled plugins are excluded; version drift alone does not change the exit code. Container image startup is the exception to the usual “run doctor after updating” flow. When openclaw gateway run starts on a new OpenClaw version, it runs safe state and plugin repairs before reporting ready. If repair cannot finish safely, startup exits and tells you to run the same image once with openclaw doctor --fix against the same mounted state/config before restarting the container normally.

Legacy state migration

openclaw doctor --fix is the only owner for persistent file-to-SQLite migrations. It validates and claims each recognized source, writes and verifies canonical rows, records a migration receipt, then removes the retired source. Runtime code does not perform lazy imports or fallback reads. For legacy workspace setup files, an existing canonical SQLite setup record wins, including milestones that are absent in SQLite. Doctor does not replay stale milestones over it. Before removing a validated setup file or interrupted claim, Doctor preserves its exact bytes beside the original as <source>.migrated.<sha256>.<unique-id>. The SQLite migration receipt records that archive path and one line per differing milestone (legacy=... canonical=...), which Doctor also prints. With no canonical setup record, Doctor imports the legacy milestones normally. A successful repair removes the runtime blocker; the next run has no workspace setup migration to repeat. Invalid files and workspace identity/version conflicts remain blocked for inspection. Doctor reports interrupted auth-profile archive recovery even when no new migration remains or you decline another migration. If recovery cannot finish, its warning includes the failure cause and leaves the pending source for recovery; do not delete it to silence the warning. doctor --fix also repairs an inconsistent completed auth migration only when its old receipt has no credential fingerprints, none of the migrated credentials remain in the current canonical store, and the preserved archive still matches the recorded source hash. Doctor reimports through the normal verified migration flow. Completed receipts with fingerprints, surviving migrated credentials, or no archive remain untouched, so removing credentials after a verified migration does not restore them from backup. Doctor also retires policy-free exec-approvals.json stubs with empty defaults and agents, including stubs without a version and those containing only socket metadata. It archives the exact bytes as exec-approvals.json.migrated.<sha256>.<unique-id>, records retirement, and leaves existing SQLite policy unchanged. When SQLite has no approvals row, Doctor imports any nonblank socket path or token so a running exec host keeps its credentials. Interrupted .doctor-importing stubs use the same repair path. Unknown fields, unsupported versions, and nonempty or malformed policy are not treated as empty stubs. For malformed legacy exec-approvals.json, Doctor preserves the original bytes and reports the first validation problem, for example agents entry #2.allowlist[1].lastUsedAt: expected a finite number. Agent entries are numbered from 1 in JavaScript Object.keys order; allowlist indices start at 0. This can differ from JSON text order, especially for numeric keys. To locate entry #2 locally, use Object.keys(JSON.parse(raw).agents)[1], where raw is the file contents. Diagnostics omit agent keys and policy values, and migration receipts contain no diagnostic detail. JSON syntax and invalid UTF-8 receive separate reasons. Repair the preserved file locally, then rerun openclaw doctor --fix with the same OPENCLAW_STATE_DIR setting (leave it unset if it was unset before). Exec approvals remain blocked until migration succeeds. Explicit repair exits nonzero while the legacy file or an interrupted .doctor-importing claim remains, before restarting any Gateway stopped for that repair. Do not delete the file or broaden its policy to bypass validation. Agent database schema upgrades are reported with the database path and the observed before and after versions, independently of media rewrites. The media persistence message appears only when transcript sessions or trajectory rows were rewritten and includes both counts. A run that does both reports both; an unchanged rerun reports neither. Device Pair and Active Memory legacy JSON imports check namespace capacity before writing. If the missing entries do not fit, doctor warns and leaves the source unchanged. These imports also verify that source keys and pre-existing destination keys remain in SQLite before reporting completion and archiving the source. A retention warning keeps the source available for inspection and retry; do not delete it to silence the warning, because it may contain state that SQLite did not retain. Resolve the capacity problem before rerunning openclaw doctor --fix. Microsoft Teams conversation, poll, and SSO token imports also verify that selected legacy keys and pre-existing destination keys remain in SQLite before archiving. Poll imports check both metadata and vote buckets; existing conversation and poll retention rules still select which legacy rows to import. If any required keys are missing, doctor warns and leaves the legacy file in place without reporting completion. Existing SQLite conversations, poll metadata, voter selections, and SSO tokens still take precedence over matching legacy values. These checks do not roll back rows already evicted during import. Doctor also reports when shared auth still uses the legacy agents/main/agent/openclaw-agent.sqlite owner. openclaw doctor --fix copies its auth profile and runtime-state rows into state/openclaw.sqlite, verifies the exact payloads, removes the source rows, and records the new ownership only after the transaction succeeds. Auth resolution has no dual-read fallback: before migration the legacy database is complete; after migration the shared state database is complete. Once relocated, deleting main no longer risks fleet credentials. If the shared target already contains every legacy profile with identical credential content, Doctor preserves the richer target and completes cleanup, including an empty legacy profile set or older row timestamps. Credential comparison ignores JSON object-key order but preserves every field; it does not select credentials by timestamp. Different credentials, source-only profiles, malformed subset payloads, or differing runtime-state rows remain conflicts. Doctor names conflicting profile IDs and whether their credentials differ, are malformed, or are missing from the target. Store metadata and runtime-state conflicts are reported separately; credential values and arbitrary metadata are never printed. Stop OpenClaw processes and back up both databases named in the warning before reconciling them locally. For each differing profile, choose the credential to retain and make its complete entry agree in both stores; copy source-only profiles into the target without replacing unrelated profiles. Resolve malformed payloads or differing store metadata and runtime state in the named records, then rerun openclaw doctor --fix. Do not delete either database or the migration receipts to silence a conflict. Pending relocation receipts retain the original source digest through interrupted cleanup. After relocation completes, main-agent rows without a pending relocation receipt remain ordinary per-agent overrides. For the retired QMD memory backend, including config rewrites and derived workspace cleanup, see Migrating from QMD. This includes retired MCP OAuth files under <state-dir>/mcp-oauth/*.json. Stop the Gateway before repair. Doctor imports valid credentials into <state-dir>/state/openclaw.sqlite, preserves an existing canonical SQLite session when both stores exist, drops the obsolete persisted OAuth state value, and uses its receipt to prevent a recreated stale file from resurrecting logged-out credentials. Retired .lock sidecars fail closed: if Doctor reports a stale owner, verify that no older OpenClaw process is running, remove that sidecar, and rerun Doctor. After explicit repair (--fix, --repair, or --yes), Doctor verifies runtime schema readiness for existing configured, default-layout, and registered databases before reporting completion, including stores whose migration failed before registration. A blocked required migration exits nonzero; stop the Gateway and other OpenClaw processes, then rerun repair. Unrelated advisory warnings, including archived transcript repair failures, do not make a ready database fail this check. Missing databases are not created by the readiness check. Doctor also discovers retired setup state and interrupted migration claims in every resolved agent workspace, active sandbox workspace, and explicitly configured agents.defaults.workspace root. That shared root is included even when an explicit multi-agent roster uses only its subdirectories. Doctor imports both <workspace>/openclaw-workspace-state.json and <workspace>/.openclaw/workspace-state.json through the existing migration; it does not assign the root to an agent or move persona and memory files. Repair exits nonzero while retained legacy state still blocks agent turns, even if its data already reached SQLite. Gateway startup and live config candidates check readiness only for the workspaces they would use, not an unused default root. An unready live candidate is rejected and the last-good runtime stays active. Stop OpenClaw processes, save the intended workspace path if the live write was rejected before persistence, and keep the retained files in place. Run openclaw doctor --fix before restarting. Readiness checks never import or delete legacy state.

Shared state SQLite compaction

See Database schemas for schema versioning, integrity checks, and downgrade recovery. openclaw doctor --state-sqlite compact is explicit offline maintenance for the canonical shared state database at <state-dir>/state/openclaw.sqlite. It does not accept an arbitrary database path, is never invoked by normal Gateway operation, and is not part of openclaw doctor --fix. The command acquires the same state ownership lock as Gateway startup and holds it through validation, checkpointing, VACUUM, and the final integrity checks. It refuses to run while a Gateway or another SQLite maintenance command owns that lock. The state lock remains active when OPENCLAW_ALLOW_MULTI_GATEWAY=1 skips the per-config Gateway singleton, so an operator shell does not need to inherit the Gateway service’s environment for maintenance to detect it. Stop the Gateway and create a verified backup first:
The command:
  1. Requires a regular file at the canonical shared-state path. A missing database is reported as skipped and exits successfully.
  2. Validates the current supported schema version and schema_meta.role = "global" before checkpointing or changing the file.
  3. Requires a non-busy wal_checkpoint(TRUNCATE). Stop any remaining OpenClaw process and retry if the checkpoint is busy.
  4. Sets auto_vacuum to INCREMENTAL, runs a full VACUUM, and checkpoints again.
  5. Runs quick_check, integrity_check, and foreign_key_check, then reapplies owner-only permissions to the database and SQLite sidecar files.
JSON output reports the database and WAL sizes, freelist pages, page size, and auto_vacuum value before and after compaction, plus reclaimed bytes and the quick_check and integrity_check results. foreign_key_check is enforced fail-closed and has no separate success field. SQLite reports auto_vacuum as 0 for none, 1 for full, and 2 for incremental. Compaction fails without mutation when the schema is old, newer than the running OpenClaw build, or belongs to an agent database. Run openclaw doctor --fix first for an older shared-state schema. Restore a compatible backup or upgrade OpenClaw for a newer schema.

Session SQLite migration

Runtime session rows and transcripts live in SQLite, by default at ~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite. 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 for the active profile instead of serving empty history. To upgrade history from an older file-backed installation, stop the Gateway, back up its state, and run openclaw doctor --fix before restarting it. openclaw doctor --session-sqlite <mode> provides targeted inspection, import, validation, and SQLite maintenance. Legacy sessions.json files are migration sources. Hot transcript JSONL files are imported and archived after successful import; archive-tier JSONL files remain support artifacts, not runtime fallbacks. The public Doctor migration path stages transcript payloads and performs branch and provider repairs in a private, temporary SQLite database instead of retaining complete histories in memory. It keeps the raw transcript untouched until archiving it through an exclusive same-filesystem move, avoiding both an extra full .pre-doctor raw copy and a rewritten intermediate file. Standalone transcript repair retains its original backup behavior. For large histories, plan space for the original JSON/JSONL files, the temporary SQLite spool, and the destination database and WAL at the same time. Keep free space on both the system temporary volume and the volume holding OpenClaw state; the resulting SQLite database can be larger than the original JSONL. Streaming reduces whole-history memory pressure, but individual records are still parsed in memory and SQLite also uses native memory. Do not size a host from the JSONL byte count or JavaScript heap limit alone; there is no fixed disk, RAM, or migration-time guarantee. Staging is removed when the operation finishes and is never used as a runtime store or resumed after an interruption; retries use the original sources and committed session data. After import, Doctor checkpoints and incrementally vacuums databases that already support auto-vacuum, retaining full integrity and foreign-key checks before and after cleanup. Databases without auto-vacuum still need a full VACUUM to enable it. Incremental cleanup frees unused pages but does not repack partially filled pages; explicit session and shared-state compact modes still run a full VACUUM. The regular openclaw doctor pass also reports canonical SQLite transcripts whose initial session header was never persisted. openclaw doctor --fix prepends a current header and rebuilds the transcript indexes in one transaction while preserving existing event IDs, parent links, row timestamps, and session-list recency. Headerless legacy or malformed transcripts remain rejected until their owning migration can validate them. Modes: Selectors:
  • Default: the configured default agent store; SQLite inspection does not require a legacy file.
  • --session-sqlite-agent <id>: one configured agent.
  • --session-sqlite-all-agents: configured agent stores plus discovered agent stores.
  • --session-sqlite-store <path>: one explicit .sqlite database or legacy sessions.json path.
dry-run, import, and validate select existing legacy sources only. An explicit .sqlite path selects no legacy targets in those modes; it is never parsed or archived as JSON. Use inspect, compact, or corruption recovery with recover for a SQLite target. Recovering or restoring archived sources from migration manifests requires the original legacy selector or agent-store discovery that includes it. Legacy sessions.json selector paths remain supported and resolve to their corresponding SQLite stores for maintenance. With the Gateway stopped and its state backed up, inspect and import legacy history:
import validates rows and transcript event counts before archiving its legacy sources. After a successful import, validate may select no legacy targets; use inspect to see the current SQLite state. While legacy sources remain, validate exits non-zero when a selected entry is missing from SQLite, a session id differs, or a transcript event count differs. When using --session-sqlite-store <path>, check that the report contains the expected target count; a nonexistent legacy source selects no targets for dry-run, import, or validate. SQLite deletes reclaim pages inside the database first; they do not necessarily shrink the database file immediately. After deleting or archiving large transcripts, run openclaw doctor --session-sqlite compact --session-sqlite-all-agents to checkpoint WAL files, run VACUUM, and report before/after database and WAL sizes. Compaction requires a regular file with the current agent schema, its durable database owner metadata, and no open handle in the doctor process. The destructive import, compact, recover, and restore modes hold the same state ownership lock as Gateway startup for their full operation; inspect, dry-run, and validate remain read-only and do not take it. Stop the Gateway first. Destructive modes fail instead of racing live writes or racing another maintenance command. A destructive --session-sqlite-store target must be inside the active state directory; set OPENCLAW_STATE_DIR to the store’s owning state directory before maintaining another installation. Existing hard-linked targets are rejected because another path can share the same database inode outside the locked state directory. The same ownership checks cover SQLite WAL, shared-memory, and rollback-journal sidecars. Each import writes a manifest under ~/.openclaw/session-sqlite-migration-runs/ before moving transcript artifacts into the archive. Recovery references stay in the current sessions directory, including for backups with old-machine absolute transcript paths. Retrying an interrupted import keeps the index and previously archived transcripts restorable. If an explicit import fails after artifacts moved, keep the Gateway stopped and run recovery:
Recovery selects the latest failed migration manifest, restores only the manifest’s archived artifacts, validates the affected targets, and prepares sanitized .failure.md and .failure.json reports. The GitHub issue body avoids transcript contents, raw environment, secrets, and unbounded config. Once an issue or browser handoff may have published a report, doctor preserves that private report artifact and its marker receipt. When no failed migration manifest exists, recovery inspects selected SQLite databases using temporary copies of their complete file sets. SQLite can roll back a valid hot journal in that disposable copy before quick_check, integrity_check, and foreign_key_check run, while the original forensic files remain untouched during inspection. Recovery attempts to repair canonical index corruption in place after schema and owner validation. Schema, owner, and I/O errors, as well as failed or refused index repairs, leave the original database in place with a diagnostic. Other confirmed corruption or orphaned sidecars preserve the DB, WAL, SHM, and rollback-journal files by renaming the whole discovered set with one .corrupt-<timestamp> suffix. A caught rename failure rolls already-moved files back before reporting failure, so a recoverable file set is not silently split. Stop the Gateway before recovery; copying or renaming an actively changing SQLite file set is unsafe and behaves differently across operating systems. With --github-issue --yes, doctor uses the GitHub CLI to create the issue in openclaw/openclaw. If the CLI is unavailable or GitHub definitively rejects the request, doctor can open the exact sanitized report in a browser when its encoded URL stays within the safe request-size bound. Without confirmation, doctor writes the local support report and skips issue creation without printing or opening a prefilled URL. Ambiguous submissions fail closed. A later doctor run reconciles the preserved marker without sending another create request, so it cannot publish a duplicate issue. Machine-readable output includes the resulting support-issue status but not the private receipt or prefilled URL. restore remains the lower-level undo operation. It uses manifest sourcePath -> archivePath records, moves archived artifacts back only when the original path is missing, reports conflicts for independently existing originals, and leaves the SQLite database in place. Publication is exclusive: a file or symbolic link created during verification is not replaced. Restore moves the original without copying its contents, and fails without consuming the archive if the filesystem cannot publish it safely. Recorded interrupted publications can be retried, including with older manifests or after the replacement SQLite database has been removed. If restore recreates a missing sessions directory, retries repeat its parent-directory durability check before consuming the archive. When several manifests recorded the same original path, restore plans all candidates before moving any of them. Identical archives are safe duplicates, and one nonempty legacy sessions.json may supersede empty copies created by older writers. Distinct nonempty indexes, distinct transcript archives, invalid archives, and archives missing without a recorded prior restore fail closed so restore cannot silently replace or hide recoverable data. After verifying the migration and current history, use openclaw update cleanup --dry-run to inspect retained recovery data without stopping the Gateway. Apply with openclaw update cleanup or openclaw update cleanup --yes --json only after stopping the Gateway and other SQLite maintenance for the same profile/state directory. This permanently retires eligible rollback originals; it does not remove current SQLite history or operator backups. Manifests remain while retained or pending artifacts need them, so interrupted cleanup can be resumed. Restore distinguishes intentional disposal, pending cleanup, and unexpected missing files. See Update cleanup.

Downgrading After Session SQLite Migration

With the Gateway stopped, use the current CLI to restore archived legacy transcript artifacts before starting an older file-backed OpenClaw version:
Older versions read sessions.json entries and the sessionFile paths recorded in those entries. After the SQLite migration, successful imports move hot JSONL transcripts into session-sqlite-import-archive/, so the older runtime cannot see that history until restore moves those manifest-recorded artifacts back to their original paths. If openclaw update cleanup already disposed of the originals, restore reports that outcome and cannot recreate them. You need an independent backup containing those legacy files; see Pre-update backups for portable-archive exclusions. Restore does not delete SQLite data. Sessions created after the SQLite flip exist only in SQLite and will not appear to the older runtime. If you later upgrade again, run the normal migration validation sequence above so OpenClaw can compare restored legacy artifacts with the SQLite rows before importing.

Notes

  • In Nix mode (OPENCLAW_NIX_MODE=1), read-only doctor checks still work, but doctor --fix, doctor --repair, doctor --yes, and doctor --generate-gateway-token are disabled because openclaw.json is immutable. Edit the Nix source for this install instead; for nix-openclaw, use the agent-first Quick Start.
  • Interactive prompts (keychain/OAuth fixes, etc.) only run when stdin is a TTY and --non-interactive is not set. Headless runs (cron, Telegram, no terminal) skip prompts.
  • Non-interactive mode skips prompts, not full provider-catalog or runtime-tool validation. Built checkout runs reuse available compiled plugin entries for these checks; intentional source overrides still execute source. See Development debugging.
  • --lint is stricter than --non-interactive: always read-only, never prompts, never applies safe migrations. Use doctor --fix or doctor --repair when you want doctor to make changes.
  • Doctor does not execute exec SecretRefs while checking secrets by default. Use --allow-exec (with or without --lint) only when you intentionally want doctor to run those configured secret resolvers.
  • Any config write (including a --fix repair) rotates a backup to ~/.openclaw/openclaw.json.bak (with a numbered .bak.1...bak.4 ring). --fix also drops unknown config keys reported by schema validation, listing each removal; it skips this while an update is in progress so partially written upgrade state is not stripped before its migration finishes.
  • If openclaw.json cannot be parsed and no last-known-good config can be recovered, doctor --fix leaves the file unchanged and exits with an error instead of writing a partial replacement. The error points to openclaw config validate for the exact parse position and explains how to edit or regenerate the config.
  • Set OPENCLAW_SERVICE_REPAIR_POLICY=external when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup.
  • Doctor reports the managed Gateway’s applied heap limit and the adaptive derivation used for the current host or container memory limit. Use openclaw gateway status for the same report outside a repair pass.
  • On Linux, doctor ignores inactive extra gateway-like systemd units and does not rewrite command/entrypoint metadata for a running systemd gateway service during repair. Stop the service first, or use openclaw gateway install --force to rewrite the managed base unit. If a systemd drop-in overrides ExecStart= or WorkingDirectory=, inspect it with systemctl --user cat <unit>.service and update or remove that drop-in yourself; reinstalling the base does not replace it. Environment= drop-ins remain supported.
  • doctor --fix --non-interactive preserves the installed gateway service definition, including during update repair. Run openclaw gateway install for a missing service, or openclaw gateway install --force from the intended installation to replace its launcher and managed environment.
  • State integrity checks detect orphan transcript files in the sessions directory. Archiving them as .deleted.<timestamp> requires interactive confirmation; --fix, --yes, and headless runs leave them in place.
  • Doctor scans historical ~/.openclaw/cron/jobs.json stores and previously configured legacy store locations for old cron job shapes, imports jobs and quarantine records into SQLite, and archives the migrated JSON files.
  • Doctor reports cron jobs with an explicit payload.model override, including provider-namespace counts and mismatches against agents.defaults.model, so scheduled jobs that do not inherit the default model are visible during auth or billing investigations.
  • Doctor reports cron jobs still marked in-flight (state.runningAtMs), which can make openclaw cron list show them as running. This check is read-only: if no Gateway is currently executing a marked job, the next cron service startup records the interrupted run and clears the marker.
  • Doctor reports legacy image-inspection policy entries named image. openclaw doctor --fix rewrites supported config allow/deny surfaces and persisted automation toolsAllow entries to view_image; old-only wildcard patterns such as image* are preserved and gain an explicit view_image, while patterns that already cover both names remain unchanged. Runtime exposes only the canonical name.
  • On Linux, doctor warns when the user’s crontab still runs the unmaintained legacy ~/.openclaw/bin/ensure-whatsapp.sh, which can misreport Gateway inactive when cron lacks the systemd user-bus environment.
  • When WhatsApp is enabled, doctor checks for a degraded Gateway event loop with local openclaw-tui clients still running. doctor --fix stops only verified local TUI clients so WhatsApp replies are not queued behind stale TUI refresh loops.
  • When HTTP(S) proxy environment variables are present but tools.web.fetch.useTrustedEnvProxy is disabled, doctor explains that web_fetch still uses direct routing, runs a short direct TLS connectivity probe, and names the explicit opt-in. It never enables proxy trust automatically.
  • Doctor rewrites legacy codex/* and openai-codex/* model refs to canonical openai/* refs across primary models, fallbacks, model allowlists, image/video generation models, heartbeat/subagent/compaction overrides, hooks, channel model overrides, cron payloads, and stale session/transcript route pins. --fix also merges legacy models.providers.codex and models.providers.openai-codex config when safe, migrates legacy openai-codex:* auth profiles and auth.order.openai-codex entries to openai:*, moves Codex intent onto provider/model-scoped agentRuntime.id: "codex" entries, removes stale whole-agent/session runtime pins, and keeps repaired OpenAI agent refs on Codex auth routing instead of direct OpenAI API-key auth.
  • Doctor reports nonempty auth.order.<provider> lists whose referenced profiles are all gone while compatible stored credentials exist. doctor --fix deletes only those stale overrides, restoring automatic per-agent credential selection; explicit empty orders, partially live lists, and orders without a compatible stored credential stay unchanged. If an active SQLite auth store is unreadable or malformed, doctor explains why it skipped this repair. Restart a running Gateway before rechecking auth status if its config reload mode does not apply the write automatically.
  • Doctor preserves legacy shared plugin-runtime caches that another installation or profile may still use and removes only genuinely dangling plugin-runtime symlinks. It relinks the host openclaw package for managed npm plugins that declare it as a peer dependency. It also repairs missing downloadable plugins referenced by config (plugins.entries, configured channels, configured provider/search settings, configured agent runtimes). During package updates, doctor skips package-manager plugin repair until the package swap completes; rerun openclaw doctor --fix afterward if a configured plugin still needs recovery. If a download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
  • Doctor repairs stale plugin config by removing missing plugin ids from plugins.allow/plugins.deny/plugins.entries, plus matching dangling channel config, heartbeat targets, and channel model overrides, when plugin discovery is healthy.
  • Doctor quarantines invalid plugin config by disabling the affected plugins.entries.<id> entry and removing its invalid config payload. Gateway startup already skips only that bad plugin so other plugins and channels keep running.
  • Doctor removes the retired plugins.entries.codex.config.codexDynamicToolsProfile; the Codex app-server always keeps Codex-native workspace tools native.
  • Doctor auto-migrates legacy flat Talk config (talk.voiceId, talk.modelId, and friends) into talk.provider + talk.providers.<provider>. Repeat doctor --fix runs no longer report/apply Talk normalization when the only difference is object key order.
  • Doctor includes a memory-search readiness check and can recommend openclaw configure --section model when embedding credentials are missing.
  • Doctor warns when no command owner is configured. The command owner is the human operator account allowed to run owner-only commands and approve dangerous actions. DM pairing only lets someone talk to the bot; if you approved a sender before first-owner bootstrap existed, set commands.ownerAllowFrom explicitly.
  • Doctor reports an info note when Codex-mode agents are configured and personal Codex CLI assets exist in the operator’s Codex home. Local Codex app-server launches use isolated per-agent homes; install the Codex plugin first if needed, then use openclaw migrate plan codex to inventory assets that should be promoted deliberately.
  • Doctor warns when skills allowed for the default agent are unavailable in the current runtime environment (missing bins, env vars, config, or OS requirements). doctor --fix can disable those unavailable skills with skills.entries.<skill>.enabled=false; install/configure the missing requirement instead if you want to keep the skill active.
  • If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (install Docker or openclaw config set agents.defaults.sandbox.mode off).
  • Doctor identifies per-agent agents.entries.<id>.sandbox Docker, browser, and prune overrides ignored under shared scope. It also warns when an agent’s explicit primary model omits fallbacks and therefore disables the defaults’ fallback chain; both diagnostics use canonical agent paths after legacy roster normalization.
  • If legacy sandbox registry files or shard directories are present (~/.openclaw/sandbox/containers.json, ~/.openclaw/sandbox/browsers.json, ~/.openclaw/sandbox/containers/, or ~/.openclaw/sandbox/browsers/), doctor reports them; --fix migrates valid entries into SQLite and quarantines invalid legacy files.
  • If gateway.auth.token/gateway.auth.password are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials. For exec-backed SecretRefs, doctor skips execution unless --allow-exec is present.
  • If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early.
  • After state-directory migrations, doctor warns when enabled default Telegram or Discord accounts depend on env fallback and TELEGRAM_BOT_TOKEN or DISCORD_BOT_TOKEN is unavailable to the doctor process.
  • Telegram allowFrom username auto-resolution (doctor --fix) requires a resolvable Telegram token in the current command path. If token inspection is unavailable, doctor reports a warning and skips auto-resolution for that pass.

macOS: launchctl env overrides

If you previously ran launchctl setenv OPENCLAW_GATEWAY_TOKEN ... (or ...PASSWORD), that value overrides your config file and can cause persistent “unauthorized” errors.