Bug type
Crash (process/app exits or hangs)
Beta release blocker
No
Summary
With the @openclaw/matrix E2EE plugin, the gateway heap grows without bound and OOM-crashes on a clock, because fake-indexeddb (the Node IndexedDB shim backing the Matrix rust-crypto store) appends every transaction to an internal array and never removes finished ones.
Steps to reproduce
A. Isolated, no Matrix — against the exact shipped fake-indexeddb@6.2.5:
import "fake-indexeddb/auto";
const db = await new Promise((res, rej) => {
const r = indexedDB.open("leaktest", 1);
r.onupgradeneeded = () => r.result.createObjectStore("s", { keyPath: "k" });
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error);
});
const raw = db._rawDatabase;
for (let i = 1; i <= 120000; i++) {
await new Promise((res, rej) => {
const t = db.transaction("s", "readwrite");
t.objectStore("s").put({ k: i % 50, v: "x".repeat(200) });
t.oncomplete = res; t.onerror = () => rej(t.error);
});
if (i % 30000 === 0) { global.gc?.(); console.log(i, "txns_array=", raw.transactions.length, "heapMB=", (process.memoryUsage().heapUsed/1048576|0)); }
}
// run: node --expose-gc repro.mjs
raw.transactions.length tracks i (never shrinks, survives GC); only ~50 records are actually stored.
B. Live gateway — run @openclaw/matrix with E2EE + several accounts in shared rooms; over the inspector, poll globalThis.indexedDB._databases and sum db.transactions.length per store. The openclaw-matrix-<account>::matrix-sdk-crypto stores climb monotonically with no inbound messages and no agent/LLM turns.
Expected behavior
A continuously-running Matrix client should not OOM from crypto-store bookkeeping. Per the IndexedDB spec a finished transaction is not retained, and every read inside fake-indexeddb already filters on _state !== "finished" — so finished transactions should not stay resident. With the one-line prune below applied, repro A holds transactions.length at 0 and heap flat (~4 MB) through 120k transactions, and the live stores hold flat at ~0.
Actual behavior
The Database.transactions array grows 1:1 with transactions ever created and is never pruned, so the heap climbs linearly to the v8 old-space ceiling and the process dies with FATAL ERROR: Ineffective mark-compacts near heap limit. Repro A:
transactions.length heapUsedMB
60,000 → 60,001 204
90,000 → 90,001 303
120,000 → 120,001 403
Live 11-account gateway: per-store totals climbed ~733 → ~1164 in 80s (+~300 every ~30s, in lockstep across accounts = the /sync fan-out). Heap grew ~95 MB/h → OOM ~every 21h on a 2 GiB heap, ~43h on 4 GiB.
OpenClaw version
2026.5.12 (root cause also present on main / v2026.5.28 by source inspection — see Additional information)
Operating system
Debian GNU/Linux 12 (bookworm) in Docker; Node v24.14.0; host kernel Linux 6.12.69 x86_64
Install method
Docker — ghcr.io/openclaw/openclaw@sha256:e2482a66682de6f540dcfd9921e410c23fd060dcd441382ff952247ee911a672, @openclaw/matrix plugin installed at runtime
Model
N/A — independent of model; reproduced with zero LLM turns (the leak is pure Matrix sync/crypto traffic)
Provider / routing chain
N/A — no inference path involved; the growth is Matrix protocol/sync/crypto only
Logs, screenshots, and evidence
# Heap snapshot at OOM — dominated by fake-indexeddb (FDB-family) objects:
# FDBTransaction ~459,469 instances 77 MB
# FDBObjectStore ~459,645 instances 35 MB
# FakeDOMStringList ~932,611 instances 28 MB
# FDB-family ≈ 9% of total node population vs ~0.1% for session-named nodes
#
# Live inspector poll (11 accounts, no human input, no agent turns):
# t+0s total 733
# t+25s total 873 (+140)
# t+50s total 1188 (+315)
# t+80s total 1308
# every growing DB is named openclaw-matrix-<account>::matrix-sdk-crypto
#
# After the prune (same gateway, same traffic): per-store count flat at ~0, heap steady.
Impact and severity
- Affected: any deployment using the
@openclaw/matrix plugin with E2EE; worse with multiple accounts in shared rooms (every protocol event is crypto-processed by all N clients → rate scales with account × sync).
- Severity: High — process OOM-crash; takes the gateway (all channels) down. Auto-restart masks it as a periodic blip.
- Frequency: Always, on a clock — ~21h (2 GiB) / ~43h (4 GiB) here; purely time/traffic driven.
- Consequence: Recurring gateway outage + lost in-flight sessions, with no inbound traffic or LLM cost — pure protocol churn.
Additional information
Distinct from #89315. This surfaced while investigating #89315, but that reporter runs feishu/mattermost and confirmed no Matrix / FDB paths — so filing the matrix-specific cause separately.
Mechanism (every hop is a source line):
crypto-runtime.ts:1 import "fake-indexeddb/auto" → sets globalThis.indexedDB.
sdk.ts:788 client.initRustCrypto({ cryptoDatabasePrefix }) (non-null prefix ⇒ persistent IndexedDB store, needed for E2EE key persistence).
- matrix-js-sdk
rust-crypto/index.ts:108-115 StoreHandle.open(storePrefix, …).
- matrix-rust-sdk wasm
matrix-sdk-crypto-wasm store.rs:88 IndexeddbCryptoStore::open_with_name(...).
- matrix-rust-sdk
crypto_store/mod.rs:1768 Database::open("{prefix}::matrix-sdk-crypto[-meta]") (the DB name in the snapshot).
- →
indexed_db_futures → web-sys → the wasm-bindgen glue __wbg_indexedDB_* in @matrix-org/matrix-sdk-crypto-wasm (body const ret = arg0.indexedDB) → reads globalThis.indexedDB = the fake-indexeddb shim → FDBFactory.open → the leaking Database.
The leak, in fake-indexeddb v6.2.5 (latest published): pushed at FDBDatabase.ts:246, marked finished at FDBTransaction.ts:309, but processTransactions() only filters into temp arrays to schedule the next run — nothing ever removes finished transactions. db.close() doesn't help: closeConnection() filters connections, never transactions.
Reopening the store doesn't reset it either: FDBFactory.open reuses the cached Database from its process-wide _databases map (databases.get(name); a new Database is created only when absent), so every reopen makes a fresh connection over the same backing Database and its growing array. The only thing that drops a Database is FDBFactory.deleteDatabase(name) → databases.delete(name), which the crypto store never calls (it would destroy the persistent E2EE keys). So the array lives for the whole process. Corroborated by the snapshot: ~13.5k FDBDatabase connection objects over ~11 underlying Databases — thousands of reopens, one persistent transactions array each.
Still on latest: v2026.5.28 extensions/matrix/package.json still pins "fake-indexeddb": "6.2.5" (matrix-js-sdk bumped to 41.6.0, store backend unchanged). Independent of the SQLite migration (#81402): that moves snapshot persistence, the live crypto store stays fake-indexeddb.
Verified workaround — one-line prune at the top of processTransactions() (spec-correct):
// fake-indexeddb build/{esm,cjs}/lib/Database.js, first line of processTransactions():
this.transactions = this.transactions.filter(t => t._state !== "finished");
Repro A then stays at 0 (heap flat ~4 MB); the live gateway's per-store count goes flat. Note: @openclaw/matrix + its fake-indexeddb install at runtime into ~/.openclaw/npm/node_modules/fake-indexeddb, not the image node_modules — patching the image / dist bundle has no effect; the live module loads from the config-volume copy.
Possible fixes:
- Prune in
fake-indexeddb's processTransactions() (as above) — continuous, keeps the array at ~0, uses the lib's own field; spec-correct. Cleanest, but patches the dependency.
- Upstream that prune into
fake-indexeddb — the array never freeing finished transactions is arguably a library bug when it's used as a long-lived store.
- Consumer-side prune in the existing 60s persist cycle — no dependency patch: at the end of each
persistIdbToDisk tick, drop finished transactions from each open database, e.g.
for (const [, database] of fakeIndexedDB._databases) {
database.transactions = database.transactions.filter(t => t._state !== "finished");
}
Bounds the array to ~60s of churn rather than ~0, and reaches into the shim's internals (_databases / Database.transactions), but requires no fork.
- Back the rust-crypto store with something other than a test-oriented shim for production (
fake-indexeddb appears to be used only by the matrix extension).
Happy to share the full snapshot analysis + inspector poller.
Bug type
Crash (process/app exits or hangs)
Beta release blocker
No
Summary
With the
@openclaw/matrixE2EE plugin, the gateway heap grows without bound and OOM-crashes on a clock, becausefake-indexeddb(the Node IndexedDB shim backing the Matrix rust-crypto store) appends every transaction to an internal array and never removes finished ones.Steps to reproduce
A. Isolated, no Matrix — against the exact shipped
fake-indexeddb@6.2.5:raw.transactions.lengthtracksi(never shrinks, survives GC); only ~50 records are actually stored.B. Live gateway — run
@openclaw/matrixwith E2EE + several accounts in shared rooms; over the inspector, pollglobalThis.indexedDB._databasesand sumdb.transactions.lengthper store. Theopenclaw-matrix-<account>::matrix-sdk-cryptostores climb monotonically with no inbound messages and no agent/LLM turns.Expected behavior
A continuously-running Matrix client should not OOM from crypto-store bookkeeping. Per the IndexedDB spec a finished transaction is not retained, and every read inside
fake-indexeddbalready filters on_state !== "finished"— so finished transactions should not stay resident. With the one-line prune below applied, repro A holdstransactions.lengthat 0 and heap flat (~4 MB) through 120k transactions, and the live stores hold flat at ~0.Actual behavior
The
Database.transactionsarray grows 1:1 with transactions ever created and is never pruned, so the heap climbs linearly to the v8 old-space ceiling and the process dies withFATAL ERROR: Ineffective mark-compacts near heap limit. Repro A:Live 11-account gateway: per-store totals climbed ~733 → ~1164 in 80s (+~300 every ~30s, in lockstep across accounts = the
/syncfan-out). Heap grew ~95 MB/h → OOM ~every 21h on a 2 GiB heap, ~43h on 4 GiB.OpenClaw version
2026.5.12 (root cause also present on
main/ v2026.5.28 by source inspection — see Additional information)Operating system
Debian GNU/Linux 12 (bookworm) in Docker; Node v24.14.0; host kernel Linux 6.12.69 x86_64
Install method
Docker —
ghcr.io/openclaw/openclaw@sha256:e2482a66682de6f540dcfd9921e410c23fd060dcd441382ff952247ee911a672,@openclaw/matrixplugin installed at runtimeModel
N/A — independent of model; reproduced with zero LLM turns (the leak is pure Matrix sync/crypto traffic)
Provider / routing chain
N/A — no inference path involved; the growth is Matrix protocol/sync/crypto only
Logs, screenshots, and evidence
Impact and severity
@openclaw/matrixplugin with E2EE; worse with multiple accounts in shared rooms (every protocol event is crypto-processed by all N clients → rate scales with account × sync).Additional information
Distinct from #89315. This surfaced while investigating #89315, but that reporter runs feishu/mattermost and confirmed no Matrix / FDB paths — so filing the matrix-specific cause separately.
Mechanism (every hop is a source line):
crypto-runtime.ts:1import "fake-indexeddb/auto"→ setsglobalThis.indexedDB.sdk.ts:788client.initRustCrypto({ cryptoDatabasePrefix })(non-null prefix ⇒ persistent IndexedDB store, needed for E2EE key persistence).rust-crypto/index.ts:108-115StoreHandle.open(storePrefix, …).matrix-sdk-crypto-wasm store.rs:88IndexeddbCryptoStore::open_with_name(...).crypto_store/mod.rs:1768Database::open("{prefix}::matrix-sdk-crypto[-meta]")(the DB name in the snapshot).indexed_db_futures→web-sys→ the wasm-bindgen glue__wbg_indexedDB_*in@matrix-org/matrix-sdk-crypto-wasm(bodyconst ret = arg0.indexedDB) → readsglobalThis.indexedDB= thefake-indexeddbshim →FDBFactory.open→ the leakingDatabase.The leak, in
fake-indexeddbv6.2.5 (latest published): pushed atFDBDatabase.ts:246, marked finished atFDBTransaction.ts:309, butprocessTransactions()only filters into temp arrays to schedule the next run — nothing ever removes finished transactions.db.close()doesn't help:closeConnection()filtersconnections, nevertransactions.Reopening the store doesn't reset it either:
FDBFactory.openreuses the cachedDatabasefrom its process-wide_databasesmap (databases.get(name); anew Databaseis created only when absent), so every reopen makes a fresh connection over the same backingDatabaseand its growing array. The only thing that drops aDatabaseisFDBFactory.deleteDatabase(name)→databases.delete(name), which the crypto store never calls (it would destroy the persistent E2EE keys). So the array lives for the whole process. Corroborated by the snapshot: ~13.5kFDBDatabaseconnection objects over ~11 underlyingDatabases — thousands of reopens, one persistent transactions array each.Still on latest: v2026.5.28
extensions/matrix/package.jsonstill pins"fake-indexeddb": "6.2.5"(matrix-js-sdkbumped to41.6.0, store backend unchanged). Independent of the SQLite migration (#81402): that moves snapshot persistence, the live crypto store staysfake-indexeddb.Verified workaround — one-line prune at the top of
processTransactions()(spec-correct):Repro A then stays at 0 (heap flat ~4 MB); the live gateway's per-store count goes flat. Note:
@openclaw/matrix+ itsfake-indexeddbinstall at runtime into~/.openclaw/npm/node_modules/fake-indexeddb, not the imagenode_modules— patching the image /distbundle has no effect; the live module loads from the config-volume copy.Possible fixes:
fake-indexeddb'sprocessTransactions()(as above) — continuous, keeps the array at ~0, uses the lib's own field; spec-correct. Cleanest, but patches the dependency.fake-indexeddb— the array never freeing finished transactions is arguably a library bug when it's used as a long-lived store.persistIdbToDisktick, drop finished transactions from each open database, e.g._databases/Database.transactions), but requires no fork.fake-indexeddbappears to be used only by the matrix extension).Happy to share the full snapshot analysis + inspector poller.