Skip to content

fix(memory): preserve retry state and embedding cache across reindex rollback#59137

Closed
TSHOGX wants to merge 1 commit into
openclaw:mainfrom
TSHOGX:fix/memory-reindex-recovery
Closed

fix(memory): preserve retry state and embedding cache across reindex rollback#59137
TSHOGX wants to merge 1 commit into
openclaw:mainfrom
TSHOGX:fix/memory-reindex-recovery

Conversation

@TSHOGX

@TSHOGX TSHOGX commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: when a safe (temp-DB) or unsafe (in-place) full memory reindex aborts mid-run, the committed index is preserved but the in-memory sync state is not — the next sync can silently converge without retrying the rolled-back work, and any successful embedding batches are thrown away with the temp DB.
  • Why it matters: transient provider failures during memory index --force should degrade to "retry the same work next sync" and should not waste the embedding batches that already succeeded before the failure. Today they do both.
  • What changed: snapshot the sync state (dirty flags, session dirty files, session deltas) before a full reindex and restore the union on rollback; add a sessionFullRetryPending sentinel so a failed full session rebuild still forces the next sync onto the full path even if sessionsDirtyFiles was cleared; during a safe reindex, mirror every successful embedding cache batch back to the original DB so the cache survives a later-batch rollback; and have seedEmbeddingCache check for the embedding_cache table on the source DB so enabling cache on an older index no longer crashes the reindex.
  • What did NOT change (scope boundary): no new config knobs, no provider API changes, no query behavior changes, no schema migrations. The committed index swap remains atomic and transport-layer retry work stays out of scope (see memory reindex aborts on transient embedding transport errors instead of retrying or splitting the batch #44166).

AI-assisted: yes. I used Claude Opus 4.7 to port the original fix onto the current manager-sync-ops / manager-embedding-ops layout and reviewed the diff and validation output.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: an offline force-reindex that aborts mid-run used to leave Embedding cache at 0 on retry, throwing away the embedding work the provider had already returned before the failure.
  • Real environment tested: macOS, Node 24.13.0, pnpm + OpenClaw checkout; two isolated $OPENCLAW_HOME dirs with identical starting DB (14408 chunks · 0 cache rows) and identical workspace (45 local markdown files). Remote provider: real SiliconFlow openai / BAAI/bge-m3, memorySearch.cache.enabled: true, cap = 500000. Git worktrees at upstream/main and fix/memory-reindex-recovery.
  • Exact steps or command run after this patch:
    1. sqlite3 $OPENCLAW_HOME/.openclaw/memory/main.sqlite 'DELETE FROM embedding_cache;' (baseline confirmed: 0 rows).
    2. OPENCLAW_HOME=... pnpm openclaw memory index --force (against real provider).
    3. Wi-Fi off ~5 s in (macOS airport disable), mid-reindex.
    4. OPENCLAW_HOME=... pnpm openclaw memory status.
    5. sqlite3 $OPENCLAW_HOME/.openclaw/memory/main.sqlite 'SELECT COUNT(*) FROM embedding_cache;'.
  • Evidence after fix: terminal capture from the isolated home-fix run — Memory index failed (main): fetch failed, then openclaw memory status prints Embedding cache: enabled (109 entries), and sqlite3 ... 'SELECT COUNT(*) FROM embedding_cache' prints 109. Row breakdown SELECT provider, model, COUNT(*) ... returns openai|BAAI/bge-m3|109 with MIN/MAX(updated_at) both landing inside the interrupted reindex window (2026-05-09 10:01:34..10:01:36). Full A/B terminal transcript is pasted under ## Evidence below.
  • Observed result after fix: on upstream/main the same offline force-reindex leaves embedding_cache at 0 rows; on fix/memory-reindex-recovery the same run preserves 109 rows written through the mirror write, which survive the temp-DB rollback. The committed chunks / files table counts are unchanged on both sides (2384 files · 14408 chunks), confirming the atomic swap is still atomic.
  • What was not tested: a live CLI repro for the session sessionFullRetryPending state machine (covered by focused manager-level tests instead); multi-process concurrent indexing; running the full pre-fix flow against a remote provider that rate-limits instead of failing DNS.
  • Before evidence: on upstream/main (64514a6) the same offline force-reindex emits Memory index failed (main): getaddrinfo ENOTFOUND api.siliconflow.cn, then openclaw memory status prints Embedding cache: enabled (0 entries) and sqlite3 ... 'SELECT COUNT(*) FROM embedding_cache' prints 0 — captured in this same evidence window.

Root Cause (if applicable)

  • Root cause: runSafeReindex / runUnsafeReindex preserved storage/vector state on rollback but did not snapshot or restore the in-memory sync state (dirty, sessionsDirty, sessionsDirtyFiles, sessionDeltas). A failed full session rebuild could clear the per-file dirty filter mid-run and leave no sentinel for the next sync to see, so shouldSyncSessionsForReindex fell back to sessionsDirty && dirtySessionFileCount > 0 and silently downgraded to an incremental sync. Separately, embedChunksInBatches accumulated cache entries until all batches finished and wrote them only to the temp DB, so a later-batch failure threw away every successful batch along with the temp DB.
  • Missing detection / guardrail: existing atomic-reindex coverage asserted that the previous index survives a failed temp reindex, but did not exercise retry-state restoration, post-compaction targeted refresh after a pending full retry, or cache preservation across temp-DB rollback.
  • Contributing context: after the memory engine moved into extensions/memory-core/src/memory/* with the helper-split layout, the old reliability work in fix(memory): harden builtin sync and reindex semantics #55497 no longer applied cleanly. This PR replays the narrow fix on top of the current layout.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • extensions/memory-core/src/memory/manager-sync-recovery.test.ts (new)
    • extensions/memory-core/src/memory/manager-session-reindex.test.ts (new)
    • extensions/memory-core/src/memory/manager.reindex-recovery.test.ts (new)
  • Scenario the test should lock in:
    • snapshotSyncState / computeRestoredSyncState union sticky dirty flags and dirty file sets; mergeSessionDeltas takes the later lastSize and the conservative pendingBytes / pendingMessages; restoreRetryStateAfterReindexRollback flips the sentinel only when the failed attempt actually began a full session rebuild.
    • shouldSyncSessionsForReindex forces a full sync when sessionFullRetryPending is set, even with zero dirty files.
    • A real manager where batch fix: add @lid format support and allowFrom wildcard handling #1 succeeds and batch Login fails with 'WebSocket Error (socket hang up)' ECONNRESET #2 throws: the committed index is preserved, the embedding_cache on the original DB has rows after the failure, a subsequent sync({ force: true }) succeeds, and enabling cache on an index that was built without the embedding_cache table no longer crashes.
  • Why this is the smallest reliable guardrail: the invariants live in local SQLite-backed state and per-method sync bookkeeping, so focused unit + seam tests exercise the contract without requiring live providers or gateway E2E.
  • Existing test that already covers this (if any): manager.atomic-reindex.test.ts covered "previous index survives failed temp reindex" but not retry state or cache preservation.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • Full memory reindex continues to keep the previous committed index on failure, and now also keeps enough dirty/retry state for the next sync to finish the rolled-back work.
  • A failed full session rebuild sets sessionFullRetryPending so the next normal sync still performs a full session retry instead of silently downgrading to an incremental pass.
  • Successful embedding batches survive a later safe-reindex rollback via cache mirroring, reducing repeated provider calls on retry.
  • Indexes built before the embedding_cache table existed can enable cache without failing reindex.

Diagram (if applicable)

Before:
[force reindex] -> [temp DB builds some state] -> [later batch failure]
               -> [committed DB restored]
               -> [dirty flags lost / full-retry intent lost / temp cache discarded]

After:
[force reindex] -> [snapshot sync state] -> [temp DB builds some state]
               -> [each successful cache batch mirrored to committed DB]
               -> [later batch failure]
               -> [committed DB restored + sync state restored + sessionFullRetryPending set]
               -> [next normal sync rebuilds the rolled-back work]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A.

Repro + Verification

Environment

  • OS: macOS (Darwin 25.2.0)
  • Runtime/container: Node 24.13.0 + pnpm + Vitest
  • Model/provider: OpenAI-compatible remote embeddings (provider: openai, model: BAAI/bge-m3) for manual repro; mocked provider in targeted tests
  • Integration/channel (if any): CLI / builtin memory index
  • Relevant config (redacted):
    • memorySearch.provider: "openai"
    • memorySearch.model: "BAAI/bge-m3"
    • memorySearch.cache.enabled: true

Steps

  1. Start from an existing committed index (14408 chunks, 0 cache rows) in an isolated $OPENCLAW_HOME.
  2. sqlite3 $OPENCLAW_HOME/.openclaw/memory/main.sqlite 'DELETE FROM embedding_cache;'
  3. OPENCLAW_HOME=... pnpm openclaw memory index --force (real SiliconFlow provider).
  4. Turn Wi-Fi off ~5 s into the reindex.
  5. Wait for the CLI to fail (on main) or cancel once retry backoff has spent the retry budget (on fix, see note below).
  6. OPENCLAW_HOME=... pnpm openclaw memory status and sqlite3 ... 'SELECT COUNT(*) FROM embedding_cache;'

Note on fix-side cancellation: the retry classifier treats fetch failed-class wrapped errors as retryable, so per-item fallback after a failed batch keeps retrying for a while; on main the error surfaced as raw getaddrinfo ENOTFOUND and exited on its own. Early-cancelling the fix run after the retries are all exhausted does not affect the 0 vs 109 evidence — those rows were already mirrored to the original DB before cancellation. Tightening that retry classifier for obviously-permanent network failures is out of scope here (see "Known follow-ups" below).

Expected

  • Force reindex fails with a transport error.
  • Committed index is preserved.
  • On main: Embedding cache count stays at 0 (successful batches are in the temp DB that gets thrown away).
  • On this branch: Embedding cache count > 0 (mirrored to the original DB).

Actual

  • On upstream/main (64514a6): Memory index failed (main): getaddrinfo ENOTFOUND api.siliconflow.cn; memory statusEmbedding cache: enabled (0 entries); SELECT COUNT(*) → 0.
  • On fix/memory-reindex-recovery-v2 (0ed55aa): memory statusEmbedding cache: enabled (109 entries); SELECT COUNT(*) → 109, all rows provider=openai / model=BAAI/bge-m3, updated_at inside the reindex window.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Validation run:

pnpm test extensions/memory-core
  Test Files  56 passed (56)
  Tests  631 passed (631)

pnpm exec oxfmt --check --threads=1 \
  extensions/memory-core/src/memory/manager-sync-ops.ts \
  extensions/memory-core/src/memory/manager-sync-recovery.ts \
  extensions/memory-core/src/memory/manager-sync-recovery.test.ts \
  extensions/memory-core/src/memory/manager-session-reindex.ts \
  extensions/memory-core/src/memory/manager-session-reindex.test.ts \
  extensions/memory-core/src/memory/manager-embedding-ops.ts \
  extensions/memory-core/src/memory/manager.reindex-recovery.test.ts \
  CHANGELOG.md
  All matched files use the correct format.

pnpm tsgo:extensions && pnpm tsgo:test
  (both green)

pnpm check:changed
  EXIT=0 (typecheck all + lint + import cycles + changelog attributions)

Manual CLI A/B (same starting DB and workspace in both isolated homes, real SiliconFlow provider, Wi-Fi turned off mid-reindex):

# upstream/main (64514a6548)
$ OPENCLAW_HOME=/.../home-main pnpm openclaw memory index --force
[memory] embeddings rate limited; retrying in 591ms
[memory] embeddings rate limited; retrying in 502ms
[memory] embeddings rate limited; retrying in 524ms
[memory] embeddings rate limited; retrying in 550ms
Memory index failed (main): getaddrinfo ENOTFOUND api.siliconflow.cn

$ OPENCLAW_HOME=/.../home-main pnpm openclaw memory status
  Indexed: 2384/45 files · 14408 chunks
  Embedding cache: enabled (0 entries)       # <- committed index preserved but cache gone

$ sqlite3 .../home-main/.openclaw/memory/main.sqlite 'SELECT COUNT(*) FROM embedding_cache;'
0
# fix/memory-reindex-recovery-v2 (0ed55aac0c)
$ OPENCLAW_HOME=/.../home-fix pnpm openclaw memory index --force
[memory] embeddings rate limited; retrying in 540ms
[memory] embeddings rate limited; retrying in 593ms
[memory] embeddings rate limited; retrying in 586ms
^C                                           # <- cancelled after retry loop exhausted

$ OPENCLAW_HOME=/.../home-fix pnpm openclaw memory status
  Indexed: 2384/45 files · 14408 chunks
  Embedding cache: enabled (109 entries)     # <- mirrored to original DB before rollback

$ sqlite3 .../home-fix/.openclaw/memory/main.sqlite \
    "SELECT provider, model, COUNT(*) FROM embedding_cache GROUP BY provider, model;"
openai|BAAI/bge-m3|109
$ sqlite3 .../home-fix/.openclaw/memory/main.sqlite \
    "SELECT datetime(MIN(updated_at)/1000, 'unixepoch', 'localtime'),
            datetime(MAX(updated_at)/1000, 'unixepoch', 'localtime'),
            COUNT(*) FROM embedding_cache;"
2026-05-09 10:01:34|2026-05-09 10:01:36|109   # <- rows written inside the reindex window

Human Verification (required)

  • Verified scenarios:
    • pnpm test extensions/memory-core — 56 files / 631 tests green, including the three new suites for recovery helpers, shouldSyncSessionsForReindex, and the manager-level rollback integration.
    • Traced the current code paths to confirm the three gaps (no sync-state snapshot on rollback, no full-retry sentinel, no mirror write) still exist on upstream/main before this PR.
    • Forced a second-batch failure in the manager-level test and asserted the original DB has embedding_cache rows after rollback.
    • Dropped embedding_cache from an existing index and reopened with cache enabled — reindex completes and the table is re-created.
    • Manual CLI A/B against real SiliconFlow (BAAI/bge-m3), Wi-Fi cut mid-reindex: main cache stays at 0 after rollback, this branch preserves 109 rows (provider+model+timestamp all from the interrupted reindex window).
  • Edge cases checked:
    • Safe reindex rollback after the first batch succeeded and a later batch threw.
    • Cache table absent in the original DB when enabling cache.
    • Targeted post-compaction session refresh preserves sessionFullRetryPending (it only clears on a full rebuild success, not on targeted refresh).
    • mergeSessionDeltas when one side has pendingBytes: 0 and the other does not — the conservative min still does not silently wipe pending work.
  • What you did not verify:
    • Live CLI repro for the session sessionFullRetryPending state machine (covered by focused manager-level tests).
    • Batch API fallback flows beyond the local regression tests.
    • Multi-process concurrent indexing.

Known follow-ups (out of scope for this PR)

  • isRetryableMemoryEmbeddingError classifies any fetch failed-wrapped error as retryable, including permanent failures like getaddrinfo ENOTFOUND. Combined with per-item fallback after a failed batch, an offline reindex on some Node versions keeps looping for a long time before the process exits — surfaces as an apparent hang. Pre-existing on main, unrelated to rollback recovery. A separate narrow PR can add ENOTFOUND to the non-retryable set or put a total-elapsed budget on the retry loop.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A.

Risks and Mitigations

  • Risk: sessionFullRetryPending adds one bit to the session sync state machine.
    • Mitigation: shouldSyncSessionsForReindex is the single point of consumption, unit-tested in isolation; targeted session refresh preserves the flag and only full-rebuild success clears it.
  • Risk: cache mirroring during safe reindex writes successful embedding batches into the original DB before the temp DB is committed.
    • Mitigation: the mirror is scoped to embedding_cache rows only (reusable work), never the searchable index rows; the committed searchable index swap remains atomic; regression coverage locks in the intended rollback behavior.
  • Risk: older indexes may not have embedding_cache.
    • Mitigation: seedEmbeddingCache probes for the table in sqlite_master before reading or mirroring; regression test exercises enabling cache on a pre-cache index.

@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: 1b05d16d12

ℹ️ 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 thread extensions/memory-core/src/memory/manager-sync-ops.ts Outdated
@greptile-apps

greptile-apps Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR addresses two related gaps in the extensions/memory-core reindex rollback path: (1) in-memory retry-state was not fully preserved on rollback, meaning a failed full session rebuild could leave the manager without the signal to do a full retry on the next sync; and (2) successful embedding batches written only to the temp DB were discarded when a later batch failed during an atomic reindex. The fix introduces a snapshot/restore mechanism for sync state, an explicit sessionFullRetryPending flag, and a cache-mirroring path that writes successful per-batch cache entries to the original DB in parallel with the temp DB during safe reindex.

Key changes:

  • snapshotSyncState / restoreSyncState / mergeSessionDeltas capture and restore the full retry-relevant state around both atomic and unsafe reindexes.
  • restoreRetryStateAfterReindexRollback unconditionally marks dirty and sessionFullRetryPending when a memory or session reindex was started but didn't complete.
  • embeddingCacheMirrorDb dual-writes each successful embedding batch into the original DB, so the cache survives a later-batch failure and temp-DB rollback.
  • seedEmbeddingCache now checks for table existence before querying, allowing older indexes (created before the cache table was added) to enable caching without crashing.
  • The targeted post-compaction sync path (lines 1079–1114 of manager-sync-ops.ts) correctly goes through clearSyncedSessionFiles, which preserves sessionFullRetryPending; only a full (non-targeted) session sync clears the flag.
  • Four new tests in manager.atomic-reindex.test.ts and one in manager.embedding-batches.test.ts lock in the rollback-retry and cache-preservation contracts.

The implementation is coherent and the invariants are correctly maintained across both the atomic (temp-DB swap) and the unsafe (test-fast) reindex paths.

Confidence Score: 5/5

Safe to merge — no data-loss or correctness regressions found; all changes are in the rollback/retry path with no impact on the successful reindex flow.

After tracing the full code paths — including confirming that the targeted post-compaction sync returns early at line 1114 via clearSyncedSessionFiles (not through the full-reset block), that upsertEmbeddingCacheInto is only called against the mirror DB when the cache table is confirmed to exist, and that the Math.min/Math.max merge for session deltas is intentionally conservative — no P0 or P1 issues remain. The single P2 observation (the Math.min semantics for lastSize) is a documentation concern, not a correctness bug.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/memory/manager-sync-ops.ts
Line: 692

Comment:
**`Math.min` for `lastSize` may over-penalise on reset**

Using `Math.min(snapshotState.lastSize, liveState.lastSize)` is intentionally conservative: if the failed reindex had already advanced `lastSize` to the new file size but the temp DB was never committed, restoring to the snapshot value prevents the next sync from skipping bytes that were only indexed in the rolled-back DB.

In practice this works correctly because `resetSessionDelta` always sets `lastSize` to `entry.size` (never below), so `live.lastSize` is either equal to or larger than `snapshot.lastSize` in the normal failure case. The one edge-case worth noting: if session delta tracking is ever reset to zero before the failure (e.g. from a future `resetIndex` that clears `sessionDeltas`), `Math.min(snapshot, 0) = 0` would force a full re-read of every session file on the next sync — correct, but potentially surprising. A brief inline note alongside the existing comment would help future readers understand this is intentional rather than a conservative shortcut.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(memory): keep full session retries p..." | Re-trigger Greptile

Comment thread extensions/memory-core/src/memory/manager-sync-ops.ts Outdated
@clawsweeper

clawsweeper Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and the useful part of this older PR is already implemented there.

Current main now carries the useful retry-state and embedding-cache recovery through the merged replacement PR, while this older source branch is conflicting and its remaining differences are obsolete branch shape and release-changelog churn.

So I’m closing this older PR as already covered on main rather than keeping a mostly-duplicated branch open.

Review details

Best possible solution:

Keep the merged replacement implementation on main and close this conflicting source branch rather than trying to merge or rebase it.

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

Yes. The source PR includes real CLI/SQLite A/B terminal proof for interrupted force reindex recovery, and current main now has focused regression tests for rollback retry state and cache mirroring.

Is this the best way to solve the issue?

Yes. The merged replacement is the better solution path because it ports the useful recovery behavior onto current main, preserves the temp-DB cleanup ordering, and avoids the source branch's obsolete release-changelog edit.

Security review:

Security review cleared: No concrete security or supply-chain concern remains; the source PR and merged replacement are memory-core SQLite retry/cache logic and tests without new dependencies, workflows, permissions, scripts, or secret handling.

AGENTS.md: found and applied where relevant.

What I checked:

Likely related people:

  • TSHOGX: Authored this source PR and is co-credited in the merged replacement commits that carried the rollback/cache recovery work onto main. (role: source repair contributor; confidence: high; commits: b4315b7e913a, 9c6a3ed1243c, a5c93b0d3a4b; files: extensions/memory-core/src/memory/manager-sync-ops.ts, extensions/memory-core/src/memory/manager-embedding-ops.ts, extensions/memory-core/src/memory/manager.reindex-recovery.test.ts)
  • Vincent Koc: Merged the replacement PR and authored the final follow-up commits that completed test routing, removed the normal PR changelog entry, and completed reindex retry recovery. (role: replacement merger and recent follow-up contributor; confidence: high; commits: 0622977cbba4, 4b24143f39c3, 2431e06ae2b6; files: extensions/memory-core/src/memory/manager-sync-ops.ts, extensions/memory-core/src/memory/manager-atomic-reindex.ts, extensions/memory-core/src/memory/manager.reindex-recovery.test.ts)
  • kunpeng-ai-lab: Provided the narrow beforeTempCleanup cleanup-contract branch discussed on this PR, which the merged replacement incorporated as review context. (role: adjacent follow-up contributor; confidence: medium; commits: cb02215b5d49; files: extensions/memory-core/src/memory/manager-sync-ops.ts, extensions/memory-core/src/memory/manager-atomic-reindex.ts, extensions/memory-core/src/memory/manager.atomic-reindex.test.ts)

Codex review notes: model internal, reasoning high; reviewed against db5e415888ae; fix evidence: commit e99ab385cfe1, main fix timestamp 2026-06-14T15:31:51+08:00.

@TSHOGX
TSHOGX force-pushed the fix/memory-reindex-recovery branch from 6808525 to 0ed55aa Compare May 9, 2026 02:25
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 9, 2026
@TSHOGX

TSHOGX commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Ported onto current main and resolved the review findings.

Re: clawsweeper "needs changes before merge"

  • Source branch was behind the manager-sync-ops / manager-embedding-ops helper-split layout. I started a fresh branch on upstream/main and replayed the three semantic fixes on top, instead of rebasing the original three commits (upsert API moved from an OO method to the functional upsertMemoryEmbeddingCache, so mechanical rebase would have meant hand-porting each conflict anyway).
  • Added the missing CHANGELOG.md Unreleased / Fixes entry.
  • oxfmt now clean on all touched files.
  • The Math.min(lastSize, ...) concern flagged by greptile and the stale-vector.dims concern flagged by codex were already addressed in bd2920f9 on the old branch; the port uses the same indexedSize / restoreVectorDims: false semantics.

What landed

  • Snapshot + restore sync state (dirty, sessionsDirtyFiles, sessionDeltas, sessionPendingFiles) around both runSafeReindex and runUnsafeReindex; unsafe path skips restoring vector.dims because the live DB has been mutated in place.
  • New sessionFullRetryPending sentinel threaded through shouldSyncSessionsForReindex so a failed full session rebuild still forces the next sync onto the full path even after sessionsDirtyFiles was cleared.
  • Per-batch cache persistence in embedChunksInBatches, with a mirror write to embeddingCacheMirrorDb (set to the original DB during safe reindex). Mirror is cleared in a finally so it can never leak across reindexes.
  • seedEmbeddingCache probes sqlite_master for the embedding_cache table before reading, so enabling cache on an older index no longer crashes reindex.
  • Recovery helpers factored into manager-sync-recovery.ts with their own unit tests; shouldSyncSessionsForReindex gets a focused test file covering the new sessionFullRetryPending branch; manager-level rollback/cache-mirror/pre-cache-table regressions live in manager.reindex-recovery.test.ts.

Acceptance

  • pnpm test extensions/memory-core → 56 files / 631 tests green (including the three new suites).
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md extensions/memory-core/src/memory/manager-sync-ops.ts extensions/memory-core/src/memory/manager-sync-recovery.ts extensions/memory-core/src/memory/manager-sync-recovery.test.ts extensions/memory-core/src/memory/manager-session-reindex.ts extensions/memory-core/src/memory/manager-session-reindex.test.ts extensions/memory-core/src/memory/manager-embedding-ops.ts extensions/memory-core/src/memory/manager.reindex-recovery.test.ts → clean.
  • pnpm tsgo:extensions + pnpm tsgo:test → green.
  • pnpm check:changedEXIT=0 (typecheck all + lint + import cycles + changelog attributions).

Diff is now 8 files, +749/−62, entirely within extensions/memory-core/src/memory/ plus the CHANGELOG line. Happy to squash into one commit on land — the single commit is already prepared on the replacement branch.

TSHOGX added a commit to TSHOGX/openclaw that referenced this pull request May 9, 2026
…rollback

- Snapshot sync state (dirty flags, session dirty files, session deltas, plus
  a new sessionFullRetryPending sentinel) before a full reindex and restore
  the union on rollback, so the next sync retries the rolled-back work
  instead of silently converging.
- During a safe temp-DB reindex, mirror successful per-batch embedding cache
  writes back to the original index so the cache survives a later batch
  failure (the temp DB is thrown away on rollback).
- seedEmbeddingCache now checks for the embedding_cache table on the source
  DB so enabling cache on an older index no longer crashes the reindex.
- Run unsafe reindex inside the same snapshot/restore wrapper without
  restoring stale vector dims (the live DB has been mutated in place).
- Focused helper unit tests and a manager-level test that force a second-batch
  failure and assert cache + retry state survive.

Ports PR openclaw#59137 onto the current memory-core helper layout.
…rollback

- Snapshot sync state (dirty flags, session dirty files, session deltas, plus
  a new sessionFullRetryPending sentinel) before a full reindex and restore
  the union on rollback, so the next sync retries the rolled-back work
  instead of silently converging.
- During a safe temp-DB reindex, mirror successful per-batch embedding cache
  writes back to the original index so the cache survives a later batch
  failure (the temp DB is thrown away on rollback).
- seedEmbeddingCache now checks for the embedding_cache table on the source
  DB so enabling cache on an older index no longer crashes the reindex.
- Run unsafe reindex inside the same snapshot/restore wrapper without
  restoring stale vector dims (the live DB has been mutated in place).
- Focused helper unit tests and a manager-level test that force a second-batch
  failure and assert cache + retry state survive.

Ports PR openclaw#59137 onto the current memory-core helper layout.
@TSHOGX
TSHOGX force-pushed the fix/memory-reindex-recovery branch from 0ed55aa to b4315b7 Compare May 9, 2026 02:34
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 9, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 9, 2026
@kunpeng-ai-lab

Copy link
Copy Markdown
Contributor

I took a pass at the clawsweeper P2 cleanup finding and prepared a narrow follow-up commit here:

https://github.com/kunpeng-ai-lab/openclaw/tree/codex/pr-59137-before-temp-cleanup

Commit: cb02215 fix(memory): close temp db before reindex cleanup

What it changes:

  • Restores an optional beforeTempCleanup callback on runMemoryAtomicReindex.
  • Wires runSafeReindex to close the temp SQLite handle through an idempotent closeTempDb() before the atomic helper removes temp sqlite files on failure.
  • Keeps the PR's retry-state restoration and embedding-cache mirror behavior unchanged.
  • Adds a focused atomic-reindex test proving the cleanup callback runs before temp index files are removed after a failed build.

Local verification:

  • pnpm test extensions/memory-core/src/memory/manager.reindex-recovery.test.ts extensions/memory-core/src/memory/manager.atomic-reindex.test.ts
    • 2 test files passed, 10 tests passed
  • pnpm test extensions/memory-core
    • 56 test files passed, 632 tests passed
  • pnpm check:changed
    • exit code 0
  • git diff --check
    • exit code 0

I do not have push access to the PR head branch, so this is available for cherry-pick or for maintainer review if useful.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress.

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@kunpeng-ai-lab

Copy link
Copy Markdown
Contributor

Following up on the remaining ClawSweeper cleanup blocker.

I prepared a narrow follow-up branch for the beforeTempCleanup / close-temp-db-before-removal path here:

https://github.com/kunpeng-ai-lab/openclaw/tree/codex/pr-59137-before-temp-cleanup

Commit: cb02215b5 fix(memory): close temp db before reindex cleanup

It keeps this PR's retry-state restoration and embedding-cache mirror behavior unchanged, and only restores the current main cleanup contract so the temp SQLite handle is closed before temp files are removed on failed safe reindex cleanup.

Local verification from that branch:

pnpm test extensions/memory-core/src/memory/manager.reindex-recovery.test.ts extensions/memory-core/src/memory/manager.atomic-reindex.test.ts
  2 test files passed, 10 tests passed

pnpm test extensions/memory-core
  56 test files passed, 632 tests passed

pnpm check:changed
  exit code 0

git diff --check
  exit code 0

The PR still shows status: ⏳ waiting on author plus compatibility/availability risk labels, so I think the next actionable step is for the PR author or a maintainer to port/cherry-pick that narrow cleanup fix into the PR head and ask ClawSweeper to re-review.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 10, 2026
@openclaw-clownfish

Copy link
Copy Markdown
Contributor

Clownfish 🐠 reef update

Thanks for the work here. Clownfish could not write to the source branch, so it opened a replacement PR rather than letting the fix drift. attribution still points back here.

Replacement PR: #92881
Source PR: #59137
Leaving this source PR open for review context and contributor follow-up.
Credit follows the fix over to the replacement PR. no sneaky treasure grab.

fish notes: model gpt-5.5, reasoning xhigh; reviewed against a5c93b0.

@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

@clawsweeper clawsweeper Bot closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: memory-core Extension: memory-core P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: L stale Marked as stale due to inactivity status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants