### Bug type
Behavior bug (incorrect output/state without crash)
### Beta release blocker
No
### Summary
When the agent database runs in rollback-journal mode (OpenClaw selects this for network or virtualized filesystems, including macOS bind mounts under Docker), `openclaw memory index` fails with `database is locked` after a 5 s stall. It always fails on the same large session. No other process or writer is involved. The memory publication Worker and the host thread deadlock on each other until the host's SQLite busy timeout expires.
### Steps to reproduce
1. Put the OpenClaw state directory on a filesystem that `src/infra/sqlite-wal-filesystem.ts` classifies as `rollback`. I used a macOS directory bind-mounted into the official Docker image under OrbStack (`stat -f -c %T` reports `fuseblk`); NFS, SMB and 9p behave the same. Confirm with `PRAGMA journal_mode` → `delete`, or file-header bytes 18–19 = `01 01`.
2. Enable memory search with `sources: ["memory", "sessions"]` and any embedding provider.
3. Have a session whose index replacement is large enough to overflow the connection's page cache. Mine was 3,262 transcript events (about 21 MB). Index it once, then let it change so it becomes dirty.
4. Stop the Gateway, then run `openclaw memory index --agent <agent> --verbose`.
### Expected behavior
The dirty session is republished and the command prints `Memory index updated`.
### Actual behavior
```
Indexing session files… 62/63
[sqlite/transaction] slow SQLite transaction hold async=false database=<state>/agents/<agent>/agent/openclaw-agent.sqlite operation=database.domain.execute elapsedMs=5207 isMainThread=false mode=immediate pid=6 threadId=4 thresholdMs=1000
Memory index failed (<agent>): database is locked
```
It fails on every run, on the same session.
### OpenClaw version
2026.9.7 (c074824) and 2026.9.8 (fc23bc8); the code path is unchanged on main (711e57e41b3)
### Operating system
macOS 27.0.1 host; official Linux container image under OrbStack 2.2.3 (Docker 29.4.0)
### Install method
Docker Compose (ghcr.io/openclaw/openclaw image), state directory bind-mounted from the host
### Model
N/A: fails during memory indexing, before any model call
### Provider / routing chain
memory search: openai-compatible embeddings (nomic-ai/nomic-embed-text-v1.5) → Fireworks API. The failure is independent of the provider: embeddings complete before the failing transaction.
### Additional provider/model setup details
_No response_
### Logs
```shell
A `node:sqlite` tracing preload (`NODE_OPTIONS=--require`) logged every transaction boundary and lock error per thread and connection:
21:19:30.931 tid=4 conn=worker BEGIN IMMEDIATE (memory publication, source.replace)
21:19:36.180 tid=0 conn=host PRAGMA data_version 5209 ms SQLITE_BUSY "database is locked"
21:19:36.202 tid=4 conn=worker ROLLBACK held=5271 ms
Host stack for the failing read:
readSqliteCacheDataVersion ← tableExists ← memorySessionTombstonesExist ← hasMemorySessionTombstone
← assertCurrent (memory-core writeChunks) ← worker-store assert ← assertOperationCurrent
← assertDispatchable/assertRequest ← MessagePort.receive (sqlite-worker-operation-admission)
```
### Screenshots, recordings, and evidence
The trigger is lock escalation, not lock tenure:
1. The Worker's `source.replace` transaction starts with `BEGIN IMMEDIATE`, which takes a RESERVED lock.
2. A large replacement spills the page cache. Under rollback journaling, the spill takes an EXCLUSIVE lock before COMMIT.
3. The Worker then requests admission and waits in `Atomics.wait` with no timeout.
4. The host handles the request in `MessagePort.receive` and runs memory-core's `assertCurrent`. That reads the tombstone table on the host's own connection to the same file. The read needs a SHARED lock, which EXCLUSIVE blocks, so the host thread sits in the busy handler for `OPENCLAW_SQLITE_BUSY_TIMEOUT_MS` (5000).
5. The read then fails, admission is refused, and the Worker rolls back.
| Experiment (same DB copy, same image) | Result |
|---|---|
| Rollback journal (bind mount), unmodified | fails 5/5 |
| Same copy on a Docker volume (WAL) | succeeds |
| Rollback journal + `PRAGMA cache_spill=0` on Worker connections (diagnostic) | succeeds |
| Rollback journal + fix moving the tombstone read before BEGIN | succeeds; `quick_check` ok |
### Impact and severity
- Affected: any install whose state directory is on NFS, SMB, 9p, or a Docker Desktop/OrbStack host bind mount, with session memory enabled and at least one large session.
- Severity: blocks memory indexing. The large session is never indexed, and the index stays dirty.
- Frequency: always, on the affected session.
- Consequence: memory search misses that session's content. Each attempt also stalls the host thread for 5 s; in the Gateway, that is the event loop.
### Additional information
- Related: #143640 (full-index publish holding the lock past other writers' busy timeout). That is a different mechanism; this deadlock needs no second writer.
- PR #164442 shortens the transcript export read snapshot. That reader was ruled out here by the trace.
- The host check cannot simply be deleted. During shadow rebuilds, the Worker's in-transaction tombstone check reads the shadow file, so the host check is the only one covering a session Forget tombstoned. The fix keeps it but runs it in `prepare`, before BEGIN, while no lock is held. A fix PR with a regression test follows.
Codex review: this still needs some work. _Reviewed October 3, 2026, 7:53 PM ET / 23:53 UTC._
**Summary**
Current main retains the reported host–worker lock cycle. The reporter’s open fix PR now owns the repair, so this issue should remain open until that work is resolved.
Likely related people: steipete and vincentkoc, both medium-confidence routing candidates.
Reproducibility: yes. a large dirty session on a rollback-journal database exercises the source-supported host-read/worker-admission cycle, corroborated by the reporter’s transaction trace. This review did not execute that reproduction.
**Next step**
Review the existing closing PR at https://github.com/openclaw/openclaw/pull/164624; do not create a competing fix PR.
<details>
<summary>Review details</summary>
Best possible solution:
Keep admission callbacks free of database reads while preserving Forget protection through preparation, workspace locking, and the worker’s transactional checks.
Do we have a high-confidence way to reproduce the issue?
Yes: a large dirty session on a rollback-journal database exercises the source-supported host-read/worker-admission cycle, corroborated by the reporter’s transaction trace. This review did not execute that reproduction.
Is this the best way to solve the issue?
Yes: separating the tombstone read from in-transaction admission is a focused repair direction; the linked PR must independently establish that Forget protection remains intact.
AGENTS.md: found and applied where relevant.
Codex review notes: model internal, reasoning medium; reviewed against [953f6b4b7a35](https://github.com/openclaw/openclaw/commit/953f6b4b7a35914f9d742c99999293a0fc5f0e96).
</details>
<details>
<summary>Label changes</summary>
Label changes:
- remove `clawsweeper:fix-shape-clear`: Current issue advisory state no longer selects this label.
- remove `clawsweeper:queueable-fix`: Current issue advisory state no longer selects this label.
Label justifications:
- `P1`: Repeated publication failures prevent large sessions from becoming searchable and can block the Gateway host thread for the busy timeout.
- `impact:session-state`: Affected session content remains missing from the memory index while indexing stays dirty.
</details>
<details>
<summary>Evidence reviewed</summary>
What I checked:
- **Host admission still reads the publication database:** writeChunks places hasMemorySessionTombstone inside assertCurrent and passes that callback into replaceSource. The callback reads the published connection even when indexing into a shadow database. ([`extensions/memory-core/src/memory/manager-embedding-ops.ts:603`](https://github.com/openclaw/openclaw/blob/4d79288452b416b43c35487c39bc8aa4fd1497e7/extensions/memory-core/src/memory/manager-embedding-ops.ts#L603), [4d79288452b4](https://github.com/openclaw/openclaw/commit/4d79288452b416b43c35487c39bc8aa4fd1497e7))
- **Worker waits for admission before committing:** The publication worker requests commit admission after replacing rows and before COMMIT. The admission transport waits in Atomics.wait without a timeout, while the host invokes the supplied current-operation callback. This supports the reported cycle when rollback-journal cache spill holds an exclusive database lock. ([`src/infra/sqlite-worker-operation-admission.ts:429`](https://github.com/openclaw/openclaw/blob/4d79288452b416b43c35487c39bc8aa4fd1497e7/src/infra/sqlite-worker-operation-admission.ts#L429), [4d79288452b4](https://github.com/openclaw/openclaw/commit/4d79288452b416b43c35487c39bc8aa4fd1497e7))
- **Fetched main has no repair on the traced boundary:** The comparison produced no changes across Memory Core, worker admission/store/broker, schema freshness, or filesystem journal policy between the checkout and fetched main at 953f6b4b7a35914f9d742c99999293a0fc5f0e96. ([`extensions/memory-core/src/memory/manager-embedding-ops.ts:622`](https://github.com/openclaw/openclaw/blob/953f6b4b7a35914f9d742c99999293a0fc5f0e96/extensions/memory-core/src/memory/manager-embedding-ops.ts#L622), [953f6b4b7a35](https://github.com/openclaw/openclaw/commit/953f6b4b7a35914f9d742c99999293a0fc5f0e96))
- **Latest release retains the same host callback:** The v2026.9.8 source also performs the session tombstone read inside writeChunks’ assertCurrent callback and passes it into replaceSource. The reporter supplies repeated failures on official 2026.9.7 and 2026.9.8 Docker images, with WAL and cache-spill diagnostic controls. ([`extensions/memory-core/src/memory/manager-embedding-ops.ts:918`](https://github.com/openclaw/openclaw/blob/fc23bc864e4553c2d215e479eeec47b67a0bf943/extensions/memory-core/src/memory/manager-embedding-ops.ts#L918), [fc23bc864e45](https://github.com/openclaw/openclaw/commit/fc23bc864e4553c2d215e479eeec47b67a0bf943))
- **Open closing PR owns the repair:** GitHub confirms https://github.com/openclaw/openclaw/pull/164624 remains open and unmerged. Its body explicitly links this issue for closure, proposes moving the tombstone read into preparation before each BEGIN, and reports successful indexing on affected database copies after applying the change. These are contributor-reported results, not reviewer execution. ([f26f58ec0253](https://github.com/openclaw/openclaw/commit/f26f58ec02538fd32ffc5548c2b2c0e661fe4a46))
- **Related reports describe different lock mechanisms:** The supplied discussion distinguishes https://github.com/openclaw/openclaw/issues/143640, which concerns concurrent writers exhausting their busy timeout during full publication, from this self-deadlock. https://github.com/openclaw/openclaw/pull/164442 shortens transcript-reader snapshots; the reporter’s trace identifies a host admission read instead.
Likely related people:
- **steipete:** Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
- **vincentkoc:** Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
</details>
<details>
<summary>How this review workflow works</summary>
- ClawSweeper keeps one durable marker-backed review comment per issue or PR.
- Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
- A fresh review can be triggered by eligible `@clawsweeper re-review` comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
- PR/issue authors and users with repository write access can comment `@clawsweeper re-review` or `@clawsweeper re-run` on an open PR or issue to request a fresh review only.
- Maintainers can also comment `@clawsweeper review` to request a fresh review only.
- Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
- Maintainer-only repair and merge flows require explicit commands such as `@clawsweeper autofix`, `@clawsweeper automerge`, `@clawsweeper fix ci`, or `@clawsweeper address review`.
- Maintainers can comment `@clawsweeper explain` to ask for more context, or `@clawsweeper stop` to stop active automation.
</details>
<!-- clawsweeper-review-version item=164621 reviewed_at=2026-10-03T23:53:02.386Z sha=na source_revision=dd85b37def927ecbd80183a6d7717043b01798e803d8994ddf3050ff5c386178 lease_owner=github-run-37162968040-1 lease_comment_id=5974740402 v=1 -->
<!-- clawsweeper-review item=164621 -->