SQLite maintenance and session migration
Shared-state compaction plus targeted session SQLite inspection, import, and recovery
Explicit SQLite maintenance runs offline, with the Gateway stopped. This page covers shared-state compaction and the targeted session SQLite modes.
SQLite keeps pages freed by deletes available for reuse inside the database file.
Older databases created with auto_vacuum=NONE cannot release those pages through
OpenClaw's routine incremental reclamation. A plain VACUUM shrinks the file once
but leaves that mode unchanged. Use the matching Doctor compact command below
with the Gateway stopped: it also enables auto_vacuum=INCREMENTAL, so routine
maintenance can reclaim pages from later deletes. Compaction preserves retained
data; it does not change session or transcript retention settings.
Normal updates now perform this conversion once for existing shared-state and
agent databases after Doctor finishes schema repairs, while maintenance still
owns the stopped Gateway. Databases already using FULL or INCREMENTAL are
left alone. The conversion needs temporary disk space and can add maintenance
time for a large database. Insufficient or unmeasurable free space, unsafe file
aliases, and an active SQLite reader defer this optional cleanup with the
matching manual command. Failed integrity checks or an unverified conversion
keep the update failed rather than restarting against uncertain state.
The automatic check conservatively measures possible SQLite temporary volumes. Low or unmeasurable space on an unused fallback volume can defer conversion too, because the runtime cannot reliably report SQLite's selected temporary directory. This deferral does not stop the normal update; use the reported offline Doctor command after checking the available space.
After conversion, normal bounded incremental reclamation can return pages freed by later deletes. It does not repack partially filled pages, so explicit offline compaction remains useful after large cleanup operations.
On Linux, a stopped Gateway service can still have child processes in its systemd cgroup. Doctor and update maintenance remain blocked until those processes exit. Inspect the service status and journal, and have the process owner stop the remaining children before retrying. A root-owned child may require administrator help even when the Gateway itself runs as a user service.
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:
openclaw gateway stop
openclaw backup create --verify
openclaw doctor --state-sqlite compact --json
openclaw gateway startThe command:
- Requires a regular file at the canonical shared-state path. A missing
database is reported as
skippedand exits successfully. - Validates the current supported schema version and
schema_meta.role = "global"before checkpointing or changing the file. - Requires a non-busy
wal_checkpoint(TRUNCATE). Stop any remaining OpenClaw process and retry if the checkpoint is busy. - Sets
auto_vacuumtoINCREMENTAL, runs a fullVACUUM, and checkpoints again. - Runs
quick_check,integrity_check, andforeign_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
Canonical session-key repair follows complete transcript-owner alias chains, including long chains in large databases. It preserves the terminal owner's session key and retained transcript history; shortening history is not required to bound the repair's stack.
Runtime session rows and transcripts live in SQLite, by default at
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite. Gateway startup uses
Doctor's exclusive maintenance owner to migrate legacy session JSON/JSONL files
before checking readiness. Runtime reads use only canonical SQLite state.
An unreadable legacy session index stays at its original path with its transcripts,
so repeated startups refuse readiness and print the active profile's
doctor --fix command until the source is repaired.
To upgrade history from an older file-backed installation, stop the Gateway
(openclaw gateway stop), back up its state (openclaw backup create --verify),
and run openclaw doctor --fix before restarting it with
openclaw gateway start.
Doctor migrates existing databases at every configured agents.entries.<id>.agentDir,
including custom paths outside the default agent tree and databases absent from the
registry. Configured session stores and retained legacy databases are also checked.
If a configured database still needs a schema migration after --fix, Doctor reports
its path and exits non-zero instead of printing Doctor complete.
Before an agent schema migration, Doctor checks database integrity in a read-only child process. Long checks print a progress line every 10 seconds with the database size, elapsed time, and current phase. Ctrl+C or SIGTERM records the interruption, cancels the inspection, and waits for admitted repairs and cleanup before exiting. An interrupted check does not authorize the next schema migration; rerun Doctor to finish. Pre-migration backups remain available.
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.
Older V2 migration receipts can record completed index moves without file identity.
Doctor compares a surviving archive with the current sessions.json: different
content imports as a new legacy index. If the archive is identical or missing,
Doctor imports history while preserving current SQLite session metadata. It does
not invent identity fields in the old receipt or delete its archive. The current
index follows normal verification and archival after import. This repair runs in
Doctor before runtime readiness, including during upgrades.
When a plugin migration is deferred, the verified import receipt also captures
unreferenced JSONL inputs. Completing the plugin migration archives those originals
with the same identity and byte checks as indexed transcripts. A transcript's
.trajectory-path.json pointer moves with it. If an earlier settlement archived the
transcript but left its receipt-verified pointer behind, the next doctor --fix
archives the pointer too. Files created after capture and changed originals are
verified separately before settlement.
File-era session path repair preserves those originals until their verified import
receipts finish archival, even after the pending plugin migration records clear.
Retries and read-only checks reuse the verified receipt, including transcripts
discovered outside sessions.json. Doctor reports one pending-plugin warning
for these retained inputs; they do not fail the completed core migration or
require doctor --session-sqlite recover. Warning-only results exit successfully.
After verified archival, Doctor retires the deferral receipt while keeping the
archive manifest for recovery. Disabling or uninstalling the owning plugin also
releases its pending obligation on the next import or repair. An enabled plugin
that is temporarily unavailable remains pending. Later explicit imports can
discover new legacy transcripts normally. While a receipt is still active, new
files outside that receipt remain in place; Doctor names the receipt, plugin,
and commands needed to finish the old migration and retry their import.
An active legacy JSONL outside that receipt is an advisory awaiting verification.
doctor --fix and --session-sqlite recover verify that its event identities and
contents are present in the owning agent's SQLite transcript. A prefix or subset
of a longer SQLite transcript is superseded, not a count mismatch. Doctor archives
the original and records superseded by SQLite (N of M events present) in its
existing migration receipt, where N is the legacy event count and M is the SQLite
event count. Missing suffix events go through the existing importer before verification;
current session settings and its active generation remain unchanged. Original
bytes stay in the migration archive for recovery. A changed, malformed, or
conflicting source that cannot be verified stays in place. A conflicting identity
or missing middle event is named in the finding. Compare those events with a
verified backup, preserve the original, and restore a corrected JSONL at the named
path before rerunning recovery. A healthy agent does not inherit another agent's failure.
When the legacy index and live transcript inputs are gone, verified historical
archives keep their existing receipts. They do not require a new legacy-index
receipt or block post-session plugin repair. The plugin's completion releases
its retained configuration.
If the original sessions.json is unavailable but the completed import receipt
still identifies the canonical database, import rebuilds its source index from
the receipt's recorded hashes. It records that repair in the existing receipt
without recreating sessions.json or replaying session metadata. Hash-matching
sources continue through import; changed or unverifiable sources remain protected
and are listed by path. Preserve those files for inspection.
A session directory does not need a legacy sessions.json to recover its history.
Doctor derives session ownership from verified transcript headers and SQLite. For
a configured agent with a pending plugin migration, it records a source index in
the existing import receipt without creating a new JSON index. For unconfigured
agents, it imports valid conversations and moves the original history into the
protected migration archive, recording each move. Trajectory-only directories are
preserved there too, without creating an empty agent database. Pending migrations
for other agents do not block this archival.
A restored copy with the recorded SHA-256 and size remains valid even when its
inode or modification time differs. --session-sqlite recover records its current
identity in the existing receipt, including when no failed migration manifest exists.
If the database file was replaced, recovery first verifies retained transcript
content against the current SQLite database before rebinding the receipt. Doctor
preserves a receipt bound to a different database in the migration ledger when
that verification fails, records why it was superseded, and checks retained
originals through the historical importer. The foreign receipt cannot certify or
block the live database. Current session settings and known archive/deletion state
remain authoritative; unindexed conversations with an unambiguous agent owner are
recovered as archived sessions. Missing originals are named with the database they
could not recover into. Preserve the reported files and restore unavailable
originals from a verified backup, then rerun openclaw doctor --fix. This warning
alone does not prevent the updater from restarting the Gateway.
Doctor also verifies formatting-only index changes against the recorded source hash and changed transcripts against complete canonical history. Verified content refreshes the receipt without overwriting current session settings or resurrecting deleted history. Changed index values and other unverifiable plugin inputs move to the protected migration archive with their validation error and recovery path in the report. For changed indexes, Doctor compares session keys and IDs with canonical SQLite and names differing metadata fields in per-session warnings. This comparison does not authorize replaying old values or accepting changed bytes as the original import. Snapshot, model-route, and integrity repairs leave these historical inputs unchanged, including after the plugin obligation completes while its source receipt remains. Canonical SQLite repairs continue. A new index appearing after an indexless import is preserved as conflicting input; it cannot inherit the earlier receipt's authority.
A retained plugin source conflict does not prevent Gateway readiness after the
core import completed. Doctor owns the repair and the Gateway keeps serving SQLite.
inspect reports the actual SQLite row count even when retained input needs repair.
Recovery reports include every remaining issue code and distinguish unresolved
findings from completed validation.
For a zero-byte retained transcript, recovery lists its .jsonl.bak-<pid>-<timestamp>
siblings and verifies the largest backup against the canonical session. Missing
suffix events use the existing historical importer; current session settings and
deleted sessions are not replayed. All backup candidates must be covered before
Doctor archives the empty original with a recoverable warning. The backup files
remain untouched. Without backups, an identified canonical session with transcript
rows can establish that the empty source is superseded. If neither source proves
the history, Doctor preserves the file and names the exact transcript and database
paths to restore from a verified backup before retrying recovery. Keep moved
transcripts and their backups together at the reported original paths.
When both a recorded legacy index and its archive are missing, Doctor verifies the remaining transcripts against canonical SQLite before reporting that the canonical transcripts are complete and the legacy index entries are informational. It preserves those live transcripts and migration records, skips another import, and allows post-session plugin repair to continue. It does not keep requesting an import for that verified history.
Doctor also discovers primary conversation transcripts omitted from the legacy registry, including timestamp-prefixed filenames. It verifies the session header, file identity, and logical owner before importing. Known historical generations remain attached to their existing session without changing its current generation or settings. History with no registry owner is recovered as an archived session only when its agent owner is unambiguous.
Rerunning import can recover primary history swept into protected archives by an earlier migration. Doctor uses retained migration manifests and archived registry lineage; it does not restore stale settings over live SQLite state. Originals stay protected, and completed recovery is recorded so later runs do not resurrect history explicitly deleted by the user. Diagnostic trajectory envelopes, deleted artifacts, unsupported files, conflicting identities, and ambiguous ownership are not converted into conversations. Deferred files remain available for recovery.
Repeated imports can leave multiple archived copies of one primary transcript.
Doctor treats copies of the same original path with the same verified size and
SHA-256 as one historical claim. It keeps one verified original and retires identical duplicates through
the existing recovery receipts, updating every retained
manifest. --session-sqlite recover also settles these archives even when the
latest failed run moved no files. Copies with different bytes stay protected and
are named in the warning; retained historical conflicts do not block update's
post-session plugin repair. Preserve the originals and migration manifests while
resolving those conflicts, then rerun openclaw doctor --fix.
Normal Doctor output and openclaw update status show at most five
historical_transcript_deferred examples per session store. Larger groups include
the total and omitted counts; other warning types remain visible. For every
finding, run openclaw doctor --session-sqlite dry-run --session-sqlite-all-agents --json.
This summary does not retire recovery references or make missing archives eligible
for cleanup. Preserve the remaining originals and migration manifests for recovery.
Changed archived registry
historical_transcript_deferred can report that an archived session registry no
longer matches its migration receipt. The receipt identifies the original file by
device, inode, modification time, size, and SHA-256; it is not an agent or install
ID. Historical archive discovery, restore, and recovery cleanup accept a device-number
change after a volume remount; inode, modification time, size, and SHA-256 must
still match. Copying, replacing, touching, or editing an archive can invalidate
that receipt.
The identity format is the same in 2026.9.4 and 2026.9.5; 2026.9.5 added historical
archive discovery that checks these older receipts.
Doctor skips historical transcript import for that store and retains the originals.
This warning alone does not indicate SQLite corruption or require a rollback.
If all expected conversations are visible, no action is needed. You can inspect
current SQLite state with
openclaw doctor --session-sqlite inspect --session-sqlite-all-agents.
If history is missing, preserve the named archive and
<state-dir>/session-sqlite-migration-runs/, make a verified backup, and seek
recovery help with the warning and inspection output. Keep transcript contents
and raw manifests private. doctor --fix does not reset an unverified fingerprint;
do not delete or edit receipts to silence the warning. Import attempts record the
skipped outcome in their migration manifest and leave existing SQLite sessions
intact.
Import staging and validation
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.
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. If a database is already in incremental auto-vacuum mode, has no
free pages, and has no WAL to checkpoint, import finalization verifies it once
and leaves its contents unchanged. 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:
| Mode | Behavior |
|---|---|
inspect | Read SQLite counts and any selected legacy-source diagnostics without importing; legacy files are not required. |
dry-run | Parse legacy entries and transcript JSONL files, count importable rows, and report issues without writing SQLite rows. |
import | Import legacy entries and transcript events into SQLite for the selected targets. |
validate | Verify selected legacy session identities and transcript contents against SQLite. |
compact | Checkpoint and VACUUM selected agent SQLite databases to reclaim free pages after large deletes or archive cleanup. |
recover | Restore a failed migration run, settle leftover active JSONL files, and report any remaining recovery issues. |
restore | Restore archived transcript artifacts from recorded migration manifests without deleting SQLite data. |
Selectors:
- Default: the configured default agent store; SQLite inspection does not require a legacy file.
--session-sqlite-agent <id>: one configured agent, or the expected database owner when paired with--session-sqlite-store(which otherwise assumesmain).--session-sqlite-all-agents: configured agent stores plus discovered agent stores.--session-sqlite-store <path>: one explicit.sqlitedatabase or legacysessions.jsonpath.
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:
openclaw doctor --session-sqlite inspect --session-sqlite-all-agents
openclaw doctor --session-sqlite dry-run --session-sqlite-all-agents --json
openclaw doctor --session-sqlite import --session-sqlite-all-agents
openclaw doctor --session-sqlite inspect --session-sqlite-all-agents --jsonimport 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 legacy transcript events are absent or conflict with
SQLite. Additional SQLite events do not fail validation.
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:
openclaw doctor --session-sqlite recover --github-issueUse --yes to authorize issue creation during noninteractive recovery. Without
it, redirected input, --non-interactive, and JSON output skip the prompt and
record why issue creation was skipped.
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 when current recovery issues remain.
Reports include the recorded run ID, failure timestamp (or not recorded for an
older journal without one), target, and failure code and error. They separate current
recovery findings from recorded migration and recovery evidence. Earlier failures
and existing reports remain available for diagnosis. A successful recovery with
zero current issues does not prepare or open a GitHub issue, even when it archived
superseded JSONL files. Repeating completed recovery is a clean no-op; blocked or
untrusted recovery artifacts still require inspection.
The JSON report keeps the combined issues evidence and adds recoveryIssues
for inspected targets. 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.
Reimporting an unchanged, manifest-recorded restored index preserves current SQLite
session metadata while reconciling its transcript history. It does not reset newer
labels, activity timestamps, or the current session pointer to the restored values.
A changed restore receipt leaves that target's originals in place with a warning,
without stopping unrelated Doctor or post-session plugin repairs. Unreadable
recovery history no longer requires an empty SQLite destination: Doctor reconciles
each session, preserves its current metadata and generation, verifies existing
transcript content, and imports new history through the normal staged importer.
Verified originals move to the recovery archive with recorded receipts. Conflicting
or malformed history with a verified SQLite owner moves to that archive as protected
input, with a target warning; it is not certified as imported or eligible for cleanup.
Unverified ownership or recovery evidence leaves originals in place. Preserve the
originals and receipts, compare the named events with a verified backup, and use
--session-sqlite restore before correcting an archived source and retrying Doctor.
These rules also apply during
updates and keep the existing archive/restore path for rollback. A newly created index with a
different file identity remains an ordinary import when recovery history is readable.
Keep recovery manifests with their original files so Doctor can distinguish the two.
Shared indexes retain a receipt for each agent's SQLite target; another owner's
receipt alone does not establish restored provenance for the selected target. Explicit custom
stores keep their existing restore/import admission outside the state directory;
that does not make their files eligible for automatic recovery cleanup.
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, other
SQLite maintenance, and database readers for the same profile/state directory.
Keep session-listing watchers stopped until cleanup exits: even read-only
connections can change WAL/SHM sidecars and invalidate verification. 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.
Hard-linked legacy artifacts
Doctor refuses a legacy sessions.json or transcript artifact when another hard
link references its inode. The diagnostic names the artifact path, device, inode,
and observed link count (nlink). Doctor does not scan for other linked paths.
The refusal protects snapshot copies from changes through a shared inode.
For a legacy source rejected before its identity was recorded for archival, stop the Gateway and create a verified backup. Copy the contents to a new temporary regular file in the same directory, preserving permissions. Verify that the copy has identical contents and a link count of one, then rename it over the reported source path and rerun the same Doctor command. Do not overwrite the source in place or create another hard link: replacing its directory entry with the fresh copy preserves the snapshot's contents without needing to find its other paths.
If an earlier migration was interrupted or the reported path is an archived
recovery artifact, preserve the files and manifests. Run
openclaw doctor --session-sqlite recover with the same profile and legacy-source
selectors first. Recorded artifacts depend on their original identities;
replacing them with copies can prevent restoration. If recovery still refuses
the artifact, retain that evidence for support instead of replacing it.
Downgrading after session SQLite migration
Follow Downgrade before starting an older release.
With writers stopped, openclaw doctor --session-sqlite restore --session-sqlite-all-agents restores manifest-recorded legacy transcript
artifacts to their original paths. This supports recovery from retained originals;
it does not reverse SQLite schema migrations or replace a pre-update backup.
Run recovery before openclaw update cleanup retires those originals. After
cleanup, restore reports intentional disposal and cannot recreate them.
Shared-state discovery uses private read-only snapshots, including for custom
stores, so a refused restore leaves the shared database and its WAL unchanged.
Sessions created only in SQLite will not appear to an older file-backed runtime. If you
upgrade again, use the normal migration validation sequence above to compare
restored artifacts with SQLite rows before importing.