Skip to content

fix(memory-core): clean up shadow reindex temp files left by the old .tmp- naming - #131568

Closed
hartmark wants to merge 4 commits into
openclaw:mainfrom
hartmark:fix/memory-core-reindex-shadow-orphan-cleanup
Closed

hartmark wants to merge 4 commits into
openclaw:mainfrom
hartmark:fix/memory-core-reindex-shadow-orphan-cleanup

Conversation

@hartmark

@hartmark hartmark commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

cleanupAgedMemoryReindexTempFiles (added in 8b7269d, "fix(sqlite): preserve migrations and reindex safety") sweeps aged, crash-left shadow reindex databases so a killed/interrupted full reindex doesn't leak its temp .memory-reindex-<uuid> files forever. But it only recognizes that current naming scheme. Deployments that ran under the prior naming scheme (.tmp-<uuid>, used before 8b7269d, see f324f7e) can still carry orphaned shadow databases under the old name — those are invisible to the sweep's resolveMemoryReindexBaseName prefix match and leak forever, since a crashed reindex never reaches its normal finally-block cleanup.

Observed in the wild: a long-lived deployment that predates 8b7269d had accumulated 357 orphaned main.sqlite.tmp-<uuid> shadow databases (plus -wal/-shm sidecars) totaling 4.7GB, none of which the existing 24h-aged cleanup ever touched because it only matched .memory-reindex-<uuid>.

Why This Change Was Made

Root-caused while investigating sustained embedding-provider traffic on a live deployment. The reindex-safety fix in 8b7269d correctly stopped new leaks under the current naming scheme, but didn't provide a migration path for shadow files already orphaned under the scheme it replaced, so those pre-existing leaks were permanent.

User Impact

Any OpenClaw deployment that has been running memory-core across the 8b7269d boundary will have its existing orphaned .tmp-<uuid> shadow reindex files (and their -wal/-shm/-journal sidecars) swept up by the normal age-gated, lock-protected cleanup the next time a reindex runs, instead of accumulating indefinitely. No behavior change for deployments that never ran the old naming scheme.

Evidence

  • resolveMemoryReindexBaseName in extensions/memory-core/src/memory/manager-db.ts now checks both the current .memory-reindex- prefix and the legacy .tmp- prefix (same UUID validation, same suffix set) before treating a file as a reindex shadow orphan.
  • Added a regression test (extensions/memory-core/src/memory/manager-db.test.ts) that writes aged .tmp-<uuid> shadow files and asserts cleanupAgedMemoryReindexTempFiles removes them.
  • oxfmt --check and oxlint clean on both changed files.
  • Autoreview (Codex, gpt-5.6-sol, high): clean, no accepted/actionable findings, "patch is correct (0.99)".

Real behavior proof: fails on pre-fix code

Reverted only manager-db.ts (kept the new test) and ran the suite — the new test fails exactly as expected, because the pre-fix matcher never recognizes the legacy .tmp-<uuid> prefix:

 ❯ extensions/memory-core/src/memory/manager-db.test.ts (10 tests | 1 failed)
   × memory manager database publication > removes aged orphan shadows left over from the pre-8b7269d1978 .tmp- naming

AssertionError: promise resolved "undefined" instead of rejecting
- Expected:
Error {
  "message": "rejected promise",
}
+ Received:
undefined

 ❯ expectPathMissing extensions/memory-core/src/memory/manager-db.test.ts:29:38
     27|
     28| async function expectPathMissing(targetPath: string): Promise<void> {
     29|   await expect(fs.access(targetPath)).rejects.toThrow("ENOENT");
       |                                      ^

 Test Files  1 failed (1)
      Tests  1 failed | 9 skipped (10)

Real behavior proof: passes after the fix

Restored manager-db.ts and re-ran the full suite (node scripts/run-vitest.mjs extensions/memory-core/src/memory/manager-db.test.ts --reporter=verbose):

 RUN  v4.1.11 /data/code/openclaw

 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > sets busy_timeout on memory sqlite connections 35ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > lazily adds recall metadata storage before publishing to an existing database 16ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > removes a stale vector table when the shadow index has no vectors 12ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > publishes the canonical path FTS table and preserves its source triggers 18ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > removes path FTS triggers when the shadow has FTS disabled 12ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > loads sqlite-vec on the target before publishing a shadow vector table 17ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > rejects a stale shadow publish after a concurrent live memory update 11ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > preserves the live embedding cache when the shadow index has caching disabled 11ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > removes aged orphan shadows but preserves young and locked shadows 7ms
 ✓ |extension-memory| extensions/memory-core/src/memory/manager-db.test.ts > memory manager database publication > removes aged orphan shadows left over from the pre-8b7269d1978 .tmp- naming 3ms

 Test Files  1 passed (1)
      Tests  10 passed (10)
   Start at  13:04:59
   Duration  12.75s (transform 9.25s, setup 1.44s, import 10.97s, tests 148ms, environment 0ms)

Update: real, non-test proof against a genuine upgraded state directory

Addresses ClawSweeper's finding that the existing evidence was test-transcript-only. Found real ammunition: my own long-lived local ~/.openclaw/memory/ directory carries 93 genuine leftover .tmp-<uuid> files (31 distinct shadow databases x 3 suffixes each) from before the 8b7269d1978 rename, several months old -- exactly the real-world deployment this PR fixes, not a synthetic fixture.

Copied that real directory to an isolated scratch location (never touched the live one) and called the actual exported cleanupAgedMemoryReindexTempFiles directly from a standalone script -- no vitest, no mocked fs, the real function against real files:

Pre-fix (checked out the parent commit's manager-db.ts against the same real data):

before: 93 legacy .tmp- orphan files
after:  93 legacy .tmp- orphan files
removed: 0

Post-fix (this PR's manager-db.ts, fresh copy of the same real data):

before: 93 legacy .tmp- orphan files
after:  0 legacy .tmp- orphan files
removed: 93

This is the "non-test Memory Core reindex against an upgraded state directory" the review asked for: a real deployment's actual accumulated legacy orphans, cleaned by the real production function, with a clean pre/post comparison on identical input.

@clawsweeper

clawsweeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@hartmark
hartmark marked this pull request as ready for review August 28, 2026 05:46
@clawsweeper clawsweeper Bot added P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 28, 2026
@clawsweeper

clawsweeper Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed September 1, 2026, 11:00 AM ET / 15:00 UTC.

ClawSweeper review

What this changes

The PR recognizes aged legacy .tmp-&lt;uuid> Memory Core shadow databases and cleans them during Doctor migration of legacy memory sidecars.

Merge readiness

⛔ Blocked until stronger real behavior proof is added - 7 items remain

Keep open: the PR reaches unarchived legacy sidecars, but misses stale shadows after an earlier Doctor run archived the base file; it can also report deletion that did not complete.

Priority: P2
Reviewed head: 076167c7ef70e3fe88d946c7b106132143ab80e1

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The root cause and initial repair are credible, but the remaining upgrade lifecycle and reporting defects block merge readiness.
Proof confidence 🦐 gold shrimp (3/6) Needs stronger real behavior proof before merge: The supplied real-data helper demonstration bypasses the introduced Doctor discovery owner, and the new test covers only an unarchived base; redacted after-fix Doctor-flow output for an already archived base is still needed. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Patch quality 🦐 gold shrimp (3/6) 2 actionable review findings remain.

Verification

Check Result Evidence
Real behavior Needs proof Needs stronger real behavior proof before merge: The supplied real-data helper demonstration bypasses the introduced Doctor discovery owner, and the new test covers only an unarchived base; redacted after-fix Doctor-flow output for an already archived base is still needed. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed 6 items Introduced migration call: The PR invokes legacy-shadow cleanup only for sources returned by legacy-sidecar discovery before archive handling.
Archived bases are not discovered: Discovery accepts directory entries ending in .sqlite; an earlier Doctor migration leaves main.sqlite.migrated, so its old main.sqlite.tmp-&lt;uuid> shadows no longer produce a cleanup source.
Deletion outcome is overstated: Each removal error is swallowed and the shadow base name is appended regardless, allowing the new Doctor change message to state that a set was removed while files remain.
Findings 2 actionable findings [P1] Discover shadows after the legacy base is archived
[P2] Report only shadow sets whose deletion completed
Security None None.

How this fits together

Memory Core stores searchable agent memory in SQLite. Doctor migrates legacy sidecars to the current agent database and archives old files, while reindex cleanup removes stale shadow databases.

flowchart LR
  A[Legacy sidecar and shadows] --> B[Doctor discovery]
  B --> C[Shadow cleanup]
  C --> D[Sidecar migration]
  D --> E[Archived legacy base]
  D --> F[Current agent database]
Loading

Before merge

  • Add real behavior proof - Needs stronger real behavior proof before merge: The supplied real-data helper demonstration bypasses the introduced Doctor discovery owner, and the new test covers only an unarchived base; redacted after-fix Doctor-flow output for an already archived base is still needed. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
  • Discover shadows after the legacy base is archived (P1) - This sweep only receives entries from legacy-sidecar discovery, which accepts files ending in .sqlite. Once a prior Doctor run renamed the base to main.sqlite.migrated, the old main.sqlite.tmp-&lt;uuid> files have no source and survive indefinitely. This repeats the earlier blocker: add archived-base discovery and a Doctor-flow regression.
  • Report only shadow sets whose deletion completed (P2) - The basename is appended after every removal attempt even when rmSync throws, so Doctor can claim orphan removal while files remain. This remains the earlier reporting blocker; verify absence before reporting removal, or surface a warning.
  • Resolve merge risk (P1) - Already-migrated installations can retain stale legacy shadows because the base is now .migrated and no longer discoverable.
  • Resolve merge risk (P1) - Doctor can claim cleanup succeeded when a file removal fails.
  • Resolve merge risk (P1) - Production code grows by 40 net lines in a cleanup fix; the final repair should consolidate discovery and verified-result ownership rather than add another parallel path.
  • Complete next step (P2) - The two remaining defects are narrow, mechanical repair work; contributor-supplied real Doctor-flow proof remains required before merge.

Findings

  • [P1] Discover shadows after the legacy base is archived — extensions/memory-core/src/migration/doctor-memory-sidecar.ts:588-590
  • [P2] Report only shadow sets whose deletion completed — extensions/memory-core/src/memory/manager-db.ts:337-343
Agent review details

Security

None.

PR surface

Source +40, Tests +96. Total +136 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 2 52 12 +40
Tests 3 102 6 +96
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 154 18 +136

Review metrics

Metric Value Why it matters
Production versus test LOC production +52/-12 (net +40); tests +102/-6 (net +96) The new production migration path has unresolved lifecycle and outcome-reporting gaps.

Stored data model

Persistent data-model change detected: migration/backfill/repair: extensions/memory-core/src/memory/manager-db.ts, migration/backfill/repair: extensions/memory-core/src/migration/doctor-memory-sidecar.ts, unknown-data-model-change: extensions/memory-core/src/memory/manager-db.ts, vector/embedding metadata: extensions/memory-core/src/migration/doctor-memory-sidecar.ts. Confirm migration or upgrade compatibility proof before merge.

Merge-risk options

Maintainer options:

  1. Complete archived-base cleanup (recommended)
    Extend Doctor to reclaim only validated aged legacy shadow sets after a prior migration archived the base, and make reported removal reflect the actual result.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Repair archived-base legacy-shadow discovery and verified-removal reporting, with focused Doctor-flow regression coverage.

Technical review

Best possible solution:

Use one Doctor-owned, strict age-gated legacy-shadow discovery path that also handles archived bases, retains lock safety, and reports only sets verified absent after deletion.

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

Yes, from source: seed an archived main.sqlite.migrated with aged main.sqlite.tmp-&lt;uuid> files and run Doctor; discovery ignores the archived base and leaves the shadows.

Is this the best way to solve the issue?

No; the added pre-archive sweep is in the right owner, but it cannot repair installations whose legacy base was archived earlier.

Full review comments:

  • [P1] Discover shadows after the legacy base is archived — extensions/memory-core/src/migration/doctor-memory-sidecar.ts:588-590
    This sweep only receives entries from legacy-sidecar discovery, which accepts files ending in .sqlite. Once a prior Doctor run renamed the base to main.sqlite.migrated, the old main.sqlite.tmp-&lt;uuid> files have no source and survive indefinitely. This repeats the earlier blocker: add archived-base discovery and a Doctor-flow regression.
    Confidence: 0.99
  • [P2] Report only shadow sets whose deletion completed — extensions/memory-core/src/memory/manager-db.ts:337-343
    The basename is appended after every removal attempt even when rmSync throws, so Doctor can claim orphan removal while files remain. This remains the earlier reporting blocker; verify absence before reporting removal, or surface a warning.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against ef44eab5ed83.

Labels

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.

Label justifications:

  • P2: This bounded upgrade-cleanup defect can leave stale local Memory Core database artifacts behind.
  • merge-risk: 🚨 compatibility: The PR changes Doctor handling of persisted legacy sidecars but does not yet cover bases already archived by a prior upgrade.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The supplied real-data helper demonstration bypasses the introduced Doctor discovery owner, and the new test covers only an unarchived base; redacted after-fix Doctor-flow output for an already archived base is still needed. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Evidence

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs extensions/memory-core/src/migration/doctor-memory-sidecar.test.ts.
  • [P1] node scripts/run-vitest.mjs extensions/memory-core/src/memory/manager-db.test.ts.
  • [P1] node scripts/run-vitest.mjs extensions/memory-core/doctor-contract-api.test.ts.
  • [P1] node scripts/check-changed.mjs -- extensions/memory-core/doctor-contract-api.test.ts extensions/memory-core/src/memory/manager-db.test.ts extensions/memory-core/src/memory/manager-db.ts extensions/memory-core/src/migration/doctor-memory-sidecar.test.ts extensions/memory-core/src/migration/doctor-memory-sidecar.ts.

What I checked:

Likely related people:

  • vincentkoc: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • steipete: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • obviyus: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Add an archived-base Doctor-flow regression and redacted after-fix output showing stale legacy shadows removed while young files and the current database survive.
  • Make deletion reporting reflect verified file removal.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • 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.

History

Review history (7 earlier review cycles)
  • reviewed 2026-08-28T05:54:02.012Z sha 53c00be57c5f3fd7efc5514d8aa8554321a86b02 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-28T11:12:39.841Z sha 53c00be57c5f3fd7efc5514d8aa8554321a86b02 :: needs real behavior proof before merge. :: none
  • reviewed 2026-08-31T15:22:54.089Z sha 53c00be57c5f3fd7efc5514d8aa8554321a86b02 :: needs maintainer review before merge. :: none
  • reviewed 2026-09-01T11:05:26.534Z sha 4aefde0 :: needs real behavior proof before merge. :: [P1] Discover orphan sets after the legacy base was archived
  • reviewed 2026-09-01T12:24:36.982Z sha 1816179 :: needs real behavior proof before merge. :: [P1] Discover orphan sets after the legacy base was archived
  • reviewed 2026-09-01T12:32:25.588Z sha 1816179 :: needs real behavior proof before merge. :: [P1] Discover orphan sets after the legacy base was archived
  • reviewed 2026-09-01T13:34:34.848Z sha 076167c :: needs real behavior proof before merge. :: [P1] Discover shadows after the legacy base is archived | [P2] Report only shadow sets that were actually removed

@hartmark

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@shojikumaru

Copy link
Copy Markdown
Contributor

Real-setup verification — tested at head 53c00be57c5f3fd7efc5514d8aa8554321a86b02

Setup: isolated checkout (git fetch origin pull/131568/head), Docker node:26, pnpm install --ignore-scripts, no secrets in env. macOS host.

After (head 53c00be5): node scripts/run-vitest.mjs extensions/memory-core/src/memory/manager-db.test.ts → 10/10 passed.

Before (merge-base 42d45857): overlaid only the PR's test file onto the merge-base, same command → 1 failed | 9 passed — the failure is the shadow-reindex temp-file cleanup scenario (expectPathMissing got a leftover .tmp- path: promise resolved "undefined" instead of rejecting).

Not covered: live gateway run (unit-level proof only).

Verified by Sho (human) + Alpha (AI agent) on a real OpenClaw setup. Happy to re-run against a new head if the branch moves.

@hartmark

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Real, non-test proof: my own long-lived local ~/.openclaw/memory/ directory has 93 genuine leftover .tmp- orphan files from before the naming change. Called the real cleanupAgedMemoryReindexTempFiles directly (no vitest, no mocked fs) against a scratch copy of that real data — 0/93 removed pre-fix, 93/93 removed post-fix. Details in the PR body.

@clawsweeper

clawsweeper Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

@clawsweeper

clawsweeper Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 31, 2026
@steipete

steipete commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I traced the actual reindex caller and upgrade path at head 53c00be57c5f3fd7efc5514d8aa8554321a86b02. The direct cleanup-function demonstration does not establish that a normal upgraded install reaches those legacy files.

In shipped v2026.6.8, src/agents/memory-search.ts:180–186 resolves the default index to stateDir/memory/<agent>.sqlite, and the reindex producer writes .tmp-<uuid> beside it. At this PR head, src/agents/memory-search.ts:265–267 always selects the canonical per-agent database; manager-sync-ops.ts:509–522 passes only that path into cleanup. The matcher therefore scans beside agents/<agent>/agent/openclaw-agent.sqlite, not the old memory/<agent>.sqlite location used in the report. Doctor's sidecar migration archives the legacy base/SQLite sidecars but does not discover these UUID-suffixed orphan files. The same mismatch remains on current main.

This needs a repair at the upgrade/doctor cleanup owner, with a full upgraded-state reproduction: seed the old directory layout, run the supported doctor/reindex flow, and verify that aged orphan files are reclaimed while active/young artifacts and the live database survive. Calling the exported helper with the old path manually bypasses the missing caller. I am leaving this open until that reachability and safe cleanup lifecycle are implemented and proven. No new-head runtime failure is claimed here; this finding is from the shipped producer, current path resolver, sole production caller, and migration source.

….tmp- naming

cleanupAgedMemoryReindexTempFiles only recognized the current
.memory-reindex-<uuid> shadow-db prefix, introduced in 8b7269d. Any
orphaned shadow database left over from before that change (named
.tmp-<uuid>, the prior scheme) was invisible to the sweep and leaked
forever, since a crashed or killed reindex never reaches the normal
finally-block cleanup.

Recognize both prefixes so the existing age-gated, lock-protected sweep
picks up orphans regardless of which naming scheme wrote them.
… sweep

Root cause found in review by steipete: the pattern widening this branch
already added (recognizing the pre-8b7269d1978 .tmp-<uuid> naming
alongside the current .memory-reindex-<uuid> one) was correct, but nothing
in production ever calls cleanupAgedMemoryReindexTempFiles with the
*legacy* path any more. src/agents/memory-search.ts's store.databasePath
always resolves to the current per-agent openclaw-agent.sqlite path via
resolveOpenClawAgentSqlitePath; the old stateDir/memory/<agentId>.sqlite
default is never revisited by any runtime reindex caller once a deployment
has upgraded past that change. Calling the exported helper directly with
the old path (as the prior test did) proved the matcher works but bypassed
the missing caller entirely -- it didn't prove any real upgraded install
ever reaches it.

The doctor migration for legacy memory sidecars
(memorySidecarStateMigration in doctor-memory-sidecar.ts) is the one place
that still knows the legacy path -- it already discovers
stateDir/memory/<agentId>.sqlite to import and archive it. Added
sweepLegacyMemorySidecarReindexOrphans, called at the start of each
legacy-path group's processing, before archiveLegacyMemorySidecar renames
the file away (cleanupAgedMemoryReindexTempFiles requires the dbPath it's
given to still be a regular file). This makes the orphan sweep reachable
from the one real caller that still visits the legacy path, independent of
whether the sidecar's data import/archival itself succeeds.

cleanupAgedMemoryReindexTempFiles now returns the shadow base names it
removed (was void) so the migration can report what happened instead of
just that the sweep ran.

New regression test (doctor-memory-sidecar.test.ts, none existed for this
migration before): seeds the real old directory layout -- a legacy
sidecar placeholder plus aged and young pre-8b7269d1978-style orphan
shadow sets -- and runs the actual memorySidecarStateMigration.migrateLegacyState
doctor flow end to end, not just the isolated cleanup helper. Confirmed
this fails on the pre-fix code (aged orphan file still present after the
migration ran) and passes after this commit.

Verification: doctor-memory-sidecar.test.ts + manager-db.test.ts 11/11;
oxfmt/oxlint clean.
@hartmark
hartmark force-pushed the fix/memory-core-reindex-shadow-orphan-cleanup branch from 53c00be to 4aefde0 Compare September 1, 2026 10:55
@hartmark

hartmark commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@steipete you're right -- fixed at the reachability boundary, not by re-testing the helper in isolation again.

src/agents/memory-search.ts's store.databasePath always resolves through resolveOpenClawAgentSqlitePath to the current per-agent openclaw-agent.sqlite; nothing in the runtime reindex caller chain ever revisits stateDir/memory/<agentId>.sqlite once a deployment has upgraded past that change. The one place that still knows the legacy path is memorySidecarStateMigration (doctor-memory-sidecar.ts) -- it already discovers and archives stateDir/memory/<agentId>.sqlite. Added sweepLegacyMemorySidecarReindexOrphans, wired into that migration's per-group loop before archiveLegacyMemorySidecar renames the file away (the cleanup helper requires the exact dbPath it's given to still be a regular file to derive the shadow basename).

New test (doctor-memory-sidecar.test.ts -- there wasn't one for this migration before) seeds the real old directory layout: a legacy sidecar placeholder plus aged and young pre-8b7269d1978-style orphan shadow sets, then runs the actual memorySidecarStateMigration.migrateLegacyState doctor flow end to end -- not the isolated helper. Confirmed it fails on the pre-fix code (aged orphan still present after the migration ran) and passes after this commit; young/active shadows and the migration's other behavior are unaffected.

cleanupAgedMemoryReindexTempFiles now returns what it removed (was void) so the migration can report a real change message instead of just "the sweep ran."

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 1, 2026
@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Sep 1, 2026
…hival exact-listing check

The legacy-orphan sweep (previous commit) now runs against every legacy
path a migration run processes, including retry-marked ones -- and, like
every other reindex-lock use in this codebase, leaves its
.reindex-lock.sqlite coordination file on disk after release() (the lock
mechanism is deliberately file-persistent; only the in-process SQLite
transaction gets released, see manager-sqlite-lease.ts). That's a real,
harmless, already-established side effect of a working sweep -- but this
test's exact before/after directory-listing comparison didn't account
for a new file category appearing, since nothing previously called
cleanupAgedMemoryReindexTempFiles against a legacy (non-canonical) path.

Excluded .reindex-lock.sqlite from both snapshots the test compares --
it's a coordination artifact orthogonal to what this test actually
verifies (archival completeness of legacy sidecars).

Caught on exact-head CI (checks-node-changed-extensions-config), not
locally: my original local run only exercised doctor-memory-sidecar.test.ts
and manager-db.test.ts directly, not the full extensions/memory-core
suite this shares an invariant with.

Verification: full extensions/memory-core suite, 100/102 files passing
(1 unrelated pre-existing timezone-formatting failure in index.test.ts,
confirmed present identically on unmodified main); doctor-contract-api.test.ts
itself 63/63.
@hartmark

hartmark commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Update: fixed a real CI regression caught on exact-head, not locally

The rebase's fix ran against every legacy path a migration processes, including retry-marked ones -- and, like every other reindex-lock use in this codebase, leaves its .reindex-lock.sqlite coordination file on disk after release() (the lock is deliberately file-persistent; only the in-process transaction gets released). Real, harmless, already-established behavior -- but doctor-contract-api.test.ts's exact before/after directory-listing comparison (copies custom vector sidecars to a discoverable retry path when the canonical retry exists) didn't account for that new file category appearing, since nothing previously called the cleanup against a legacy path.

Excluded .reindex-lock.sqlite from both directory snapshots the test compares -- it's a coordination artifact orthogonal to what the test verifies (archival completeness).

My original local proof only ran doctor-memory-sidecar.test.ts + manager-db.test.ts directly; this only surfaced on the full extensions/memory-core suite, which is exactly what checks-node-changed-extensions-config runs on exact-head CI.

Verification: full extensions/memory-core suite, 100/102 files passing (1 unrelated pre-existing timezone-formatting failure in index.test.ts, confirmed present identically on unmodified main); doctor-contract-api.test.ts itself 63/63.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 1, 2026
…reindex-orphan migration test

Matches other extension tests' convention (openclaw/plugin-sdk/test-env's
useAutoCleanupTempDirTracker) instead of a manual mkdtemp/afterEach pair --
flagged by check:changed's temp-dir migration warning.
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 1, 2026
hartmark added a commit to hartmark/openclaw that referenced this pull request Sep 1, 2026
….tmp- naming (openclaw#131568)

Combined-deploy squash of openclaw#131568. See that PR for the
full commit history, review discussion, and live-verification evidence.
@hartmark

hartmark commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing this for now.

On my own deployment this saved real disk space — cleaned up the orphaned legacy .tmp-<uuid> shadow files by hand (1071 files + 18 aged current-naming ones the existing sweep also hadn't caught, ~5.6GB total, host was at 98% disk usage), so the issue is solved for me directly.

I don't have the review bandwidth right now to rebase this past the conflict with main (a sibling function's return type changed since this was opened) and close out ClawSweeper's remaining findings. Rather than let the underlying bug disappear with this PR, filed #136284 to keep it visible for whoever has bandwidth to pick it up — this PR's diff and evidence (357-file / 4.7GB real-world case, root cause, regression test, before/after proof) are still there for a future attempt to build on.

We tried to get eyes on it — leaving it here for now.

@hartmark hartmark closed this Sep 2, 2026
@hartmark
hartmark deleted the fix/memory-core-reindex-shadow-orphan-cleanup branch September 2, 2026 12:07
@hartmark
hartmark restored the fix/memory-core-reindex-shadow-orphan-cleanup branch September 2, 2026 12:22
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 merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants