Skip to content

Refactor runtime state into SQLite#78595

Merged
steipete merged 1159 commits into
mainfrom
exp-vfs
May 13, 2026
Merged

Refactor runtime state into SQLite#78595
steipete merged 1159 commits into
mainfrom
exp-vfs

Conversation

@steipete

@steipete steipete commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR is the database-first runtime/state refactor for #78096. It moves OpenClaw away from scattered JSON, JSONL, sidecar SQLite, lock-file, pruning, and truncation-repair paths toward a typed SQLite storage model with one shared control-plane database and one data-plane database per agent.

Current rule of the road:

  • openclaw.json, plugin manifests, Git checkouts, explicit credential source files, and real user workspaces remain file-backed configuration or user content.
  • Runtime data, caches, ledgers, auth profiles, session rows, transcript events, plugin state, scheduler state, task state, and agent scratch/artifact state live in SQLite.
  • Legacy files are migration inputs, explicit debug/export output, or real workspace files. They are not compatibility stores that normal runtime code keeps reading and writing.
  • Runtime code must not write session JSON, transcript JSONL, auth-profile JSON, plugin-cache JSON, or file-backed ACPX/session stores anymore.

Refs #78096

Current State

Updated May 10, 2026.

The PR branch has the main architecture in place:

  • global state DB for gateway/control-plane data
  • per-agent DB for session/transcript/VFS/artifact data
  • generated Kysely types from SQL schemas
  • native node:sqlite runtime access
  • doctor/migrate as the legacy-file import boundary
  • SQLite session rows and transcript events
  • SQLite auth profiles and model catalog/cache state
  • SQLite plugin state/blob stores
  • SQLite ACPX session state
  • SQLite VFS, tool artifact, and run artifact stores
  • worker-prepared run plumbing with serializable storage boundaries

The active cleanup pass is now focused on deleting/refactoring stale file-era assumptions from tests and runtime seams. Recent cleanup removed or rewired tests that still pretended active sessions/transcripts/auth lived in JSON/JSONL files, and tightened docs so transcript JSONL is migration-only input, never a runtime locator or bridge.

This is not merge-ready until the latest cleanup is pushed and the current Crabbox/Testbox validation is green.

Reviewer Mental Model

Review this as a storage-boundary refactor, not as a single session bug fix.

  • Global database = control plane. ~/.openclaw/state/openclaw.sqlite owns gateway-wide registries, plugin state/blob rows, cron/task/flow state, queues, sandbox/browser/device/pairing data, migration bookkeeping, auth-profile KV rows, and shared caches.
  • Per-agent database = data plane. ~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite owns sessions, transcripts, ACP/subagent rows, VFS rows, tool/run artifacts, and agent-local cache data for that one workspace.
  • Configuration remains files. openclaw.json, plugin manifests, Git checkouts, and real workspace files stay file-backed by design. Credential source files remain only where the user explicitly configured a file-backed secret source; auth profile runtime state itself is SQLite-backed.
  • Doctor/migrate is the compatibility boundary. Legacy JSON/JSONL/sidecar SQLite files are imported once, verified, recorded, and removed after successful migration. Runtime code should not silently import, repair, prune, or rewrite legacy session/cache/auth files while handling normal gateway traffic.
  • Kysely is the typed query layer. SQL files are the schema source of truth, generated Kysely types come from a real temporary SQLite database, and runtime uses OpenClaw's native node:sqlite dialect rather than a second SQLite runtime driver.
  • Worker/VFS readiness is part of the shape. Agent workers get serializable storage boundaries and open their own global/per-agent DB handles; gateway-owned streaming, cancellation, and delivery stay in the parent process.

Database Layout

Global State Database

~/.openclaw/state/openclaw.sqlite

Owns data that must be visible across the gateway and agents:

  • agent registry and per-agent database discovery
  • auth-profile secrets/state KV rows and SQLite-backed refresh coordination
  • plugin runtime state, plugin blobs, setup/migration bookkeeping, and installed-plugin indexes
  • device, node, pairing, sandbox, browser, and delivery queue state
  • cron job definitions, cron runtime state, and cron run history
  • task and Task Flow registries
  • commitments and small scoped key-value state
  • migration/backup metadata and shared cache rows

The global DB is intentionally not where large agent-local transcripts, VFS rows, or artifacts accumulate.

Per-Agent Database

~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite

Owns state that belongs to one agent/workspace:

  • session entries and route metadata
  • transcript events, snapshots, and checkpoint metadata
  • ACP/subagent run metadata
  • SQLite-backed VFS rows for agent scratch/workspace data
  • tool artifacts and run artifacts
  • agent-local runtime/cache rows that can later become searchable or indexable

The global database registers where each agent database lives. The agent database owns the write-heavy per-agent lane, so transcripts and artifacts do not become a global gateway bottleneck.

Schema And Access

The database schemas are SQL-first and generated into TypeScript from real temporary SQLite databases:

  • src/state/openclaw-state-schema.sql -> src/state/openclaw-state-db.generated.d.ts and src/state/openclaw-state-schema.generated.ts
  • src/state/openclaw-agent-schema.sql -> src/state/openclaw-agent-db.generated.d.ts and src/state/openclaw-agent-schema.generated.ts
  • owner-local schemas, such as proxy capture, keep their own SQL and generated files near the owning store

Runtime code uses Kysely over OpenClaw's native node:sqlite dialect. better-sqlite3 is used only by kysely-codegen for dev-time introspection; it is not a runtime driver. Runtime stores use typed Kysely queries, transactions, unique keys, conflict handling, and explicit row patches instead of whole-file mutation.

Refactor Scope

This branch moves or removes the file-era runtime paths across the codebase:

  • Session indexes move from sessions.json to per-agent session_entries rows.
  • Transcript runtime reads/writes move from JSONL tail/read/append paths to SQLite transcript tables.
  • Runtime session identity becomes { agentId, sessionKey }, not a legacy storePath or transcript locator.
  • Auth profiles move from auth-profiles.json / auth-state.json into SQLite-backed state, with doctor-owned import for legacy files.
  • Gateway session history, reset, compaction, route updates, ACP metadata, heartbeat-isolated runs, fast abort, subagent spawning, TUI session state, and auto-reply session updates use SQLite row helpers.
  • Channel/plugin runtime APIs carry session identity and SQLite-backed metadata instead of requiring callers to pass session-store file paths.
  • Plugin SDK surfaces expose database-backed session and plugin-state helpers; legacy path helpers are narrowed to migration/export/debug boundaries.
  • Memory search session indexing now uses transcript terminology end to end; host exports list/build session transcript helpers, targeted sync queues sessionTranscripts, and QMD/builtin indexers no longer expose session-file helper names.
  • ACPX service state uses a SQLite/plugin-state-backed ACP session store instead of the upstream file-backed runtime store.
  • Plugin/runtime ledgers, installed-plugin indexes, task/flow state, cron state, commitments, delivery queues, sandbox/browser registries, pairing/device state, model catalog/cache state, and small plugin caches move into SQLite-backed stores.
  • Extension-owned caches and state have explicit migration hooks where ownership belongs to the plugin, including Discord, Matrix, Microsoft Teams, Telegram, QQBot, Feishu, Nostr, iMessage, and related channel/plugin surfaces.
  • SQLite VFS, tool artifact, and run artifact stores give agents a database-backed workspace/scratch option.
  • Worker-prepared agent runs use serializable storage boundaries so Node workers can open their own database-backed VFS/cache/artifact stores while gateway-owned streaming, cancellation, and reply operations stay in the parent process.

Removed File-Era Machinery

SQLite replaces these old runtime concepts:

  • session file locks and lock doctor lanes
  • whole-store sessions.json rewrite queues
  • runtime JSON/JSONL import fallbacks
  • startup session repair and pruning side effects
  • transcript truncation repair and JSONL backup writes
  • disk-budget cleanup based on transcript file existence
  • transcript locators as a runtime bridge
  • path-shaped session APIs in gateway/channel/plugin hot paths
  • test-only sessions.json row-store shims that let new runtime tests keep pretending session state was a file
  • upstream ACPX file session stores; ACPX runtime session records now go through SQLite-backed plugin state
  • auth-profile file reads/writes in normal runtime paths; legacy auth files are doctor migration inputs
  • ad hoc plugin/channel cache JSON files where the data is runtime state

Migration Model

openclaw doctor --fix and openclaw migrate are the migration boundary.

Migration builds the global and per-agent SQLite databases from legacy inputs, verifies imported rows, records migration runs, and removes old source files only after successful import. Runtime code should not silently import, repair, prune, or rewrite legacy session/cache/auth files while handling normal gateway traffic.

Migration inputs include:

  • legacy sessions.json -> per-agent session rows
  • legacy transcript *.jsonl -> per-agent transcript events/snapshots
  • legacy auth-profiles.json, auth-state.json, auth.json, and OAuth credential files -> SQLite auth-profile state where applicable
  • legacy cron jobs.json, jobs-state.json, and run JSONL files -> shared cron tables
  • legacy task and Task Flow sidecar stores -> shared task/flow tables
  • legacy plugin-state sidecars and plugin JSON caches -> shared plugin state/blob tables or plugin-owned migration hooks
  • legacy sandbox/browser registry JSON -> shared registry tables
  • legacy channel/plugin caches -> plugin-owned SQLite state through setup/doctor migration hooks

Future state changes should add schema migrations and typed stores instead of adding new sidecar files.

Backup, Export, And Vacuum

Backups should be database-first archives:

  • include a compact snapshot of state/openclaw.sqlite
  • include every agents/<agentId>/agent/openclaw-agent.sqlite
  • include configuration, explicit credential source files, plugin manifests, and requested workspace/artifact exports
  • vacuum/compact database snapshots into one archive so restore has one obvious database-first path

Session export remains a support/debug/workspace-export feature, not a second canonical runtime state system.

VFS, Workers, And PI Boundary

Agent workspace state is designed for disk, hybrid, or SQLite-backed VFS operation. The per-agent database owns VFS rows, tool artifacts, run artifacts, and scoped caches so worker-prepared runs can receive a serializable storage boundary.

Worker execution stays experimental behind settings while the storage boundary settles. The intended shape is one worker per active run first; pooling can come later after lifecycle and database contention are proven.

This also continues internalizing PI behind OpenClaw-owned facades. Session state, transcript storage, filesystem behavior, worker preparation, runtime accounting, and provider/runtime contracts are represented through OpenClaw-owned stores and contracts instead of letting PI define the durable layout.

Review Guide

For each former file-backed state owner, ask: where does this data live now?

Acceptable homes are:

  • global SQLite database
  • per-agent SQLite database
  • explicit configuration/secret file
  • real user workspace file
  • migration-only legacy input
  • debug/export-only output
  • temporary scratch selected by the agent filesystem mode

Anything else is probably leftover file-era state and should be deleted, migrated, or renamed until the boundary is clear.

Latest Validation

Current Crabbox/Blacksmith Testbox run after the latest rebase/cleanup:

Recent local/remote validation during the cleanup pass:

  • pnpm check:database-first-legacy-stores
  • pnpm db:kysely:check
  • pnpm lint:kysely
  • pnpm tsgo:core
  • pnpm tsgo:extensions
  • git diff --check
  • pnpm test src/channels/plugins/bundled.shape-guard.test.ts
  • pnpm test extensions/matrix/src/migration-config.test.ts extensions/matrix/src/matrix/sdk/idb-persistence.test.ts
  • focused session/transcript/gateway/auth/OAuth/doctor/model-auth/secrets/Matrix/WhatsApp/reset Vitest shards

Recent Testbox findings were stale assumptions rather than new product design issues: imessage setup metadata missing a legacy migration discovery hint, Matrix tests leaking SQLite plugin state across cases, Matrix IndexedDB snapshot tests using async env-scoped state around SQLite, and channel/gateway fixtures that still asserted pre-SQLite file/session paths. Those are being cleaned up as part of the current pass before this PR is considered ready.

Real behavior proof

  • Behavior or issue addressed: Database-first refactor branch remains on the SQLite runtime path after the main rebase and Baileys dependency-policy cleanup.
  • Real environment tested: Local OpenClaw checkout on branch exp-vfs at 3601c492e4, macOS, Node 22, pnpm 11.
  • Exact steps or command run after this patch: node scripts/check-database-first-legacy-stores.mjs
  • Evidence after fix: Terminal output from the real checkout after the pushed fix:
$ node scripts/check-database-first-legacy-stores.mjs
database-first legacy store guard: runtime source looks OK.
  • Observed result after fix: Runtime source guard accepted the branch with no active session/transcript JSONL, transcript locator, session file, or sidecar store regressions outside doctor/e2e legacy migration fixtures.
  • What was not tested: Full release validation was not rerun locally; CI will rerun on the pushed branch.

@steipete
steipete requested a review from a team as a code owner May 6, 2026 19:12
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: whatsapp-web Channel integration: whatsapp-web app: web-ui App: web-ui gateway Gateway runtime cli CLI command changes security Security documentation scripts Repository scripts commands Command implementations agents Agent runtime and tooling channel: feishu Channel integration: feishu extensions: anthropic extensions: openai extensions: minimax extensions: cloudflare-ai-gateway extensions: kimi-coding extensions: kilocode extensions: codex extensions: lmstudio size: XL maintainer Maintainer-authored PR labels May 6, 2026
@clawsweeper

clawsweeper Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The branch moves OpenClaw runtime state across sessions, transcripts, auth, plugin/channel state, device/app state, schedulers, caches, VFS, artifacts, docs, workflows, and tests into global and per-agent SQLite stores.

Reproducibility: Do we have a high-confidence way to reproduce the issue? Yes for the blocking review findings by source inspection of PR head and discussion evidence: the import, session-key, Node engine, dependency, and transcript replacement paths are directly visible. The PR itself is a broad refactor rather than a single current-main bug reproduction.

Real behavior proof
Needs real behavior proof before merge: Needs real behavior proof before merge: the linked Testbox run is cancelled and for an older head, so the contributor should add current-head terminal output, logs, screenshots, recordings, or linked artifacts with private data redacted; updating the PR body should trigger a fresh ClawSweeper review, or a maintainer can comment @clawsweeper re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Protected security and maintainer labels, broad runtime architecture scope, supply-chain changes, current P1 findings, and missing current-head proof require maintainer-led review rather than an automated repair lane.

Security
Needs attention: The patch introduces concrete security and supply-chain concerns in legacy auth/exec migration paths and package resolution changes.

Review findings

  • [P1] Restore Node 22 compatibility — package.json:1844
  • [P1] Keep runtime dependencies pinned — package.json:1750-1773
  • [P1] Reject unreadable auth state before deleting source — src/commands/doctor/legacy/auth-profile-state.ts:30-33
Review details

Best possible solution:

Keep the database-first architecture under maintainer review, restore supported runtime and dependency contracts, fix the concrete migration/session correctness blockers, and require completed current-head Testbox or equivalent real behavior proof before merge.

Do we have a high-confidence way to reproduce the issue?

Do we have a high-confidence way to reproduce the issue? Yes for the blocking review findings by source inspection of PR head and discussion evidence: the import, session-key, Node engine, dependency, and transcript replacement paths are directly visible. The PR itself is a broad refactor rather than a single current-main bug reproduction.

Is this the best way to solve the issue?

Is this the best way to solve the issue? No, not in its current form. The database-first direction may be right, but this branch needs migration-safety fixes, dependency/runtime contract cleanup, green current-head validation, and real behavior proof before it is a maintainable merge path.

Full review comments:

  • [P1] Restore Node 22 compatibility — package.json:1844
    Changing the root engine to >=24.0.0 blocks installs on the currently supported Node 22.16+ runtime, and the same branch removes the Node 22 compatibility CI lane. Keep Node 22 support working unless maintainers explicitly approve a release-wide runtime floor change with docs, installers, CI, and package metadata updated together.
    Confidence: 0.91
  • [P1] Keep runtime dependencies pinned — package.json:1750-1773
    This refactor changes several runtime dependencies from exact versions to caret ranges and swaps @openclaw/fs-safe from the npm release to a GitHub commit source. That broadens package resolution and supply-chain behavior outside the storage refactor; keep the reviewed exact package sources unless this dependency policy change is separately approved.
    Confidence: 0.88
  • [P1] Reject unreadable auth state before deleting source — src/commands/doctor/legacy/auth-profile-state.ts:30-33
    loadJsonFile(statePath) can return undefined for malformed or unreadable JSON, which is immediately coerced, saved, and then unlinked. A repair run can wipe newer SQLite auth state and remove the recoverable source instead of skipping the bad import.
    Confidence: 0.88
  • [P1] Skip stale exec approvals before deleting legacy state — src/commands/doctor/legacy/exec-approvals.ts:34-35
    This importer writes the legacy exec-approvals.json snapshot into the live SQLite approval store and removes the source without checking existing SQLite state. A delayed doctor run can roll allowlists or socket policy back to stale file-era values.
    Confidence: 0.9
  • [P1] Normalize session keys before optimistic patches — src/config/sessions/store.ts:116-120
    patchSessionEntry may read an existing row through normalized-key fallback, but it applies the compare-and-swap using the raw options.sessionKey. Non-canonical inputs can conflict repeatedly or write an alias row instead of updating the canonical session row.
    Confidence: 0.86
  • [P1] Merge legacy transcript files instead of replacing rows — src/commands/doctor/state-migrations.ts:307-312
    Legacy transcript import calls replaceSqliteSessionTranscriptEvents, which deletes existing rows for the session before inserting the current file. Real installs can have multiple legacy files for one session, so later imports can drop events imported from earlier files.
    Confidence: 0.87

Overall correctness: patch is incorrect
Overall confidence: 0.9

Security concerns:

  • [high] Legacy auth import can delete live auth state — src/commands/doctor/legacy/auth-profile-state.ts:30
    Malformed or unreadable legacy auth state can be coerced to an empty payload, saved into SQLite, and then removed as a source file, risking loss of newer auth routing or usage state.
    Confidence: 0.88
  • [high] Legacy exec import can restore stale execution policy — src/commands/doctor/legacy/exec-approvals.ts:34
    The exec-approvals migration writes file-era policy into the live SQLite store and deletes the source without an existing-state or recency guard, so stale allowlists or socket policy can be reintroduced.
    Confidence: 0.9
  • [medium] Dependency resolution is broadened in a storage refactor — package.json:1750
    Runtime dependencies are changed from exact pins to caret ranges, and the filesystem safety package is moved from an npm release to a GitHub commit source, increasing supply-chain review scope beyond the stated SQLite refactor.
    Confidence: 0.86

Acceptance criteria:

  • Review current-head PR diff for all legacy import paths that delete source files or overwrite live SQLite rows.
  • Run targeted session/transcript/auth/exec migration tests after fixes.
  • Run the repo's changed gate and a current-head Testbox/Crabbox proof lane before merge.

What I checked:

  • Live PR state is protected and broad: Live API state shows the PR is open, unmerged, non-draft, head 0e631f6197fd1d29f6e6f3fa2392cb3aa316e4a0, with 3,059 changed files and protected security and maintainer labels. (0e631f6197fd)
  • Current proof run is cancelled and stale: The PR body points to Blacksmith Testbox run 25626062293, but the run completed as cancelled on head db1d0402b169215ebd94d9935795c52a89933160, not the current PR head. (db1d0402b169)
  • Legacy auth import can erase state on unreadable input: At PR head, importLegacyAuthProfileStateFileToSqlite coerces loadJsonFile(statePath) directly, saves the result, and unlinks the source; malformed or unreadable input can become empty persisted auth state before deletion. (src/commands/doctor/legacy/auth-profile-state.ts:30, 0e631f6197fd)
  • Legacy exec approvals overwrite live SQLite policy: At PR head, importLegacyExecApprovalsFileToSqlite writes the legacy raw file into SQLite and immediately removes the file without checking for newer live approval state. (src/commands/doctor/legacy/exec-approvals.ts:34, 0e631f6197fd)
  • Session patch writes with raw keys: At PR head, patchSessionEntry reads through getSessionEntry(options) but applies the SQLite patch under options.sessionKey, so non-canonical keys can conflict or create alias rows. (src/config/sessions/store.ts:116, 0e631f6197fd)
  • Transcript import replaces same-session rows: At PR head, legacy transcript import calls replaceSqliteSessionTranscriptEvents; the discussion includes a populated-install migration test showing this replacement path dropped 1,424 transcript rows compared with merge-based import. (src/commands/doctor/state-migrations.ts:307, 0e631f6197fd)

Likely related people:

  • steipete: The PR branch is authored by steipete, and current-main history shows recent work by steipete on session-store writes, legacy state migration coverage, auth-profile state helpers, package/dependency refreshes, and exec-approval refactors. (role: feature owner and recent area contributor; confidence: high; commits: 897bac5b8cd3, b4437047f477, 94275f13fb77; files: src/config/sessions/store.ts, src/infra/state-migrations.ts, src/agents/auth-profiles/state.ts)
  • vincentkoc: Current-main history for src/infra/state-migrations.ts includes migration cold-path and cycle-extraction work by vincentkoc, making them a likely reviewer for the legacy migration boundary. (role: recent migration contributor; confidence: medium; commits: 97ee0c6fd339, 74e7b8d47b18; files: src/infra/state-migrations.ts)
  • MertBasar0: Current-main history for src/config/sessions/store.ts includes recent state-aware failover and lane suspension work touching persisted session state behavior. (role: recent session-store contributor; confidence: medium; commits: 029ca8c26884; files: src/config/sessions/store.ts)
  • Takhoffman: Current-main history for src/infra/exec-approvals.ts includes the local exec-policy CLI feature, which is adjacent to the SQLite execution-approval migration risk in this PR. (role: exec-policy feature contributor; confidence: medium; commits: 4bf94aa0d669; files: src/infra/exec-approvals.ts)

Remaining risk / open question:

  • The diff is extremely large and still active, so the listed findings are sampled blockers rather than an exhaustive review of all 3,059 files.
  • The migration model touches auth, execution policy, device/pairing state, channel caches, transcripts, and plugin state, so stale-file imports can become security or data-loss regressions if not consistently guarded.
  • The current proof linked in the PR is cancelled and stale relative to the current head, so real upgraded-runtime behavior remains unproven.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 71d2e41bb072.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c536838794

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!isSqliteSessionStoreBackendEnabled(env)) {
return null;
}
const agentId = resolveAgentIdFromSessionStorePath(storePath) ?? DEFAULT_AGENT_ID;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive SQLite agent scope from configured store path

When session.store uses a non-canonical path (for example a custom template not shaped like .../agents/<id>/sessions/sessions.json), this code silently falls back to DEFAULT_AGENT_ID. After this commit moved runtime session reads/writes to SQLite, that fallback causes all such stores to be loaded/saved under the main SQLite partition, so non-main agent data can be mixed into the wrong store scope and agent-specific queries can return incorrect sessions.

Useful? React with 👍 / 👎.

Comment thread src/commands/doctor/state-migrations.ts Outdated
Comment on lines +765 to +767
const sessionsTargetDir = path.join(stateDir, "agents", targetAgentId, "sessions");
const sessionsTargetStorePath = path.join(sessionsTargetDir, "sessions.json");
const legacySessionEntries = safeReadDir(sessionsLegacyDir);
const hasTargetJsonSessionStore = includeSessions && fileExists(sessionsTargetStorePath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detect legacy session JSON using session.store override

Doctor legacy migration detection is hardcoded to $STATE_DIR/agents/<defaultAgent>/sessions/sessions.json, so installs that previously persisted session metadata at a custom session.store path are skipped during openclaw doctor --fix. Because this commit removed runtime JSON session-store fallback, those users can lose visible session history after upgrading until they manually import from the custom path.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added the extensions: memory-core Extension: memory-core label May 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c567352a42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +35 to +43
async function withTranscriptAppendQueue<T>(queueKey: string, fn: () => Promise<T>): Promise<T> {
const previous = transcriptAppendQueues.get(queueKey) ?? Promise.resolve();
let releaseCurrent!: () => void;
const current = new Promise<void>((resolve) => {
releaseCurrent = resolve;
});
const tail = previous.catch(() => undefined).then(() => current);
transcriptAppendQueues.set(queueKey, tail);
await previous.catch(() => undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore cross-process locking for transcript appends

appendSessionTranscriptMessage now serializes writes only through the in-memory transcriptAppendQueues map, so concurrent writers from different Node processes are no longer coordinated. When a gateway process and another process (e.g., CLI/worker) append to the same agentId/sessionId at the same time, both can read the same pre-append event list and then insert independently, which can duplicate idempotent messages and produce conflicting parentId chains in SQLite transcripts.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor Author

Landed via squash merge: f91de52

Proof on rebased head 12f038f7e90214b9054901cdf5695bb53f6eedca:

  • pnpm check:database-first-legacy-stores
  • pnpm db:kysely:check
  • pnpm lint:kysely
  • OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts src/infra/openclaw-root.test.ts test/package-manager-config.test.ts test/vitest-unit-fast-config.test.ts test/scripts/docker-build-helper.test.ts test/scripts/install-sh.test.ts test/scripts/lint-suppressions.test.ts --reporter=verbose
  • pnpm test src/commands/oauth-tls-preflight.test.ts -- --reporter=verbose
  • pnpm test test/plugin-npm-runtime-build.test.ts src/flows/search-setup.test.ts src/pairing/allow-from-store-read.test.ts src/commands/doctor/shared/legacy-config-migrate.validation.test.ts -- --reporter=verbose
  • pnpm test src/plugins/contracts/plugin-sdk-runtime-api-guardrails.test.ts extensions/acpx/src/process-lease.test.ts extensions/codex/src/app-server/run-attempt.context-engine.test.ts extensions/discord/src/monitor/native-command.think-autocomplete.test.ts -- --reporter=verbose
  • git diff --check && node scripts/check-no-conflict-markers.mjs

GitHub Actions green: CI 25798157047, CodeQL Critical Quality 25798157064, CodeQL 25798157038, Blacksmith Testbox 25798157056, Blacksmith Build Artifacts Testbox 25798157054, Dependency Change Awareness 25798156760.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12f038f7e9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +58 to +59
writeAcpEventLedgerSnapshotToSqlite(store, { env });
await fs.rm(filePath, { force: true }).catch(() => undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip stale ACP ledger imports when SQLite already has newer rows

This import unconditionally writes the legacy acp/event-ledger.json snapshot into the live SQLite ACP replay store and then deletes the file. Because runtime now persists the same state via createSqliteAcpEventLedger/writeStoreToSqliteDb in src/acp/event-ledger.ts, running openclaw doctor --fix after normal runtime activity can roll sessions/events back to older file-era values and make that downgrade durable once the legacy source is removed.

Useful? React with 👍 / 👎.

Comment on lines +166 to +167
writeConfigHealthStateToSqlite(params.env, () => params.baseDir, state);
await fs.rm(filePath, { force: true }).catch(() => undefined);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject empty legacy config-health snapshots before replacing state

Casting any object-shaped JSON to ConfigHealthState here allows {} (or objects without entries) to be treated as a successful import, then writeConfigHealthStateToSqlite clears config_health_entries when no rows are present and the source file is deleted. Since runtime updates this store continuously (src/config/io.ts), a delayed doctor run with a stale/empty legacy file can wipe newer SQLite health fingerprints with no recovery path from the removed source.

Useful? React with 👍 / 👎.

Comment on lines +74 to +78
state.entries[syncKey] = parsed;
imported++;
}
await writeMemoryWikiSourceSyncState(params.vaultRoot, state);
await fs.rm(sourcePath, { force: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Memory Wiki source file when sync entries are skipped

This importer explicitly skips malformed source-sync.json entries, but still writes the partial result and unconditionally deletes the entire legacy file. In mixed valid/invalid payloads, skipped sync keys become unrecoverable, so operators cannot correct the bad rows and rerun migration to import the missing entries.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: macos App: macos app: web-ui App: web-ui channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: qa-channel Channel integration: qa-channel channel: qqbot channel: signal Channel integration: signal channel: slack Channel integration: slack channel: synology-chat channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations dependencies-changed PR changes dependency-related files docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: acpx extensions: amazon-bedrock extensions: anthropic extensions: anthropic-vertex extensions: chutes extensions: cloudflare-ai-gateway extensions: codex extensions: device-pair extensions: diagnostics-otel Extension: diagnostics-otel extensions: diffs extensions: github-copilot extensions: google extensions: kilocode extensions: kimi-coding extensions: llm-task Extension: llm-task extensions: lmstudio extensions: memory-core Extension: memory-core extensions: memory-lancedb Extension: memory-lancedb extensions: memory-wiki extensions: microsoft extensions: minimax extensions: nvidia extensions: oc-path extensions: ollama extensions: openai extensions: opencode extensions: openrouter extensions: phone-control extensions: qa-lab extensions: tts-local-cli extensions: vllm extensions: xai extensions: zai gateway Gateway runtime maintainer Maintainer-authored PR plugin: azure-speech Azure Speech plugin plugin: bonjour Plugin integration: bonjour plugin: file-transfer plugin: google-meet plugin: migrate-claude plugin: migrate-hermes scripts Repository scripts security Security documentation size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.