[codex] Fix memory close sync race#86765
Conversation
|
Codex review: needs maintainer review before merge. Reviewed May 26, 2026, 9:21 PM ET / 01:21 UTC. Summary PR surface: Source +31, Tests +62. Total +93 across 2 files. Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main registers this.syncing only after provider initialization, while close snapshots pendingSync before await points. I did not run tests in this read-only review, but the PR adds deterministic coverage for the interleaving. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge Security Review detailsBest possible solution: Land the narrow memory-core fix after normal maintainer and CI review, then close #86702 if the merge is accepted as the canonical fix. Do we have a high-confidence way to reproduce the issue? Yes, source inspection gives a high-confidence reproduction path: current main registers this.syncing only after provider initialization, while close snapshots pendingSync before await points. I did not run tests in this read-only review, but the PR adds deterministic coverage for the interleaving. Is this the best way to solve the issue? Yes, the merge-result approach is a narrow fix: register in-flight sync work before provider initialization, then delay provider and database teardown until the current sync has settled. It preserves the current native watcher close path in the merge result. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against f6599ede0d5b. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +31, Tests +62. Total +93 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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. How this review workflow works
|
|
ClawSweeper PR egg ✨ Hatched: 🌱 uncommon Frosted Clawlet Hatch commandComment Hatchability rules:
Rarity: 🌱 uncommon. What is this egg doing here?
|
|
@clawsweeper re-review I added a standalone runtime proof to the PR body. It runs the real |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Verification for head ec42633: Behavior addressed: memory close now drains providers through one explicit helper path, preserving the provider-created-during-shutdown retry while removing the duplicated close call sequence.
|
Refs #86702
Summary
MemoryIndexManager.sync()as in-flight before awaiting provider initialization soclose()can see syncs that are blocked inside provider setup.Root Cause
close()capturedthis.syncingbefore awaiting provider initialization and watcher teardown. A sync that was already started but still awaitingensureProviderInitialized()could attachthis.syncingafter that stale snapshot, lettingclose()proceed to close provider and database resources while the sync continued.Real Behavior Proof
Behavior addressed:
MemoryIndexManager.close()no longer closes embedding providers or the SQLite database before already-started sync work settles, including syncs blocked in provider initialization.Real environment tested: Local sparse checkout on macOS arm64 with Node v24.13.1 and installed workspace dependencies. The proof runs a standalone Node runtime script against the real
MemoryIndexManager, real SQLite index, and the public memory embedding provider registry. The script creates a temporary workspace and memory file, startssync({ force: true }), blocks provider creation, callsclose(), then releases provider init and holds the realembedBatch()call before allowing sync to finish.Exact steps or command run after this patch:
Evidence after fix:
Observed result after fix: While sync was blocked inside the real
embedBatch()path,close()had not settled, the embedding provider had not been closed, and SQLite still answeredSELECT 1. After releasing sync,close()completed without error, the provider closed exactly once, and SQLite reporteddatabase is not open, which shows resource teardown happened after the in-flight sync settled.What was not tested: The proof uses a local registered provider adapter to make the race deterministic; it does not call external embedding APIs. Full repository lint/check was not run locally. The repo
run-oxlint.mjswrapper tries to prepare extension package boundary artifacts for unrelated sparse-checkout extensions, so the PR uses direct touched-fileoxfmt/oxlintplus the relevant memory tests.Verification
NODE_NO_WARNINGS=1 node --import tsx /private/tmp/openclaw-86765-proof.mjs- passed, runtime proof abovenode scripts/run-vitest.mjs run --config test/vitest/vitest.extension-memory.config.ts --configLoader runner extensions/memory-core/src/memory/index.test.ts extensions/memory-core/src/memory/manager.async-search.test.ts extensions/memory-core/src/memory/manager.readonly-recovery.test.ts- passed, 3 files / 32 tests./node_modules/.bin/oxfmt --check extensions/memory-core/src/memory/manager.ts extensions/memory-core/src/memory/index.test.ts- passed./node_modules/.bin/oxlint --tsconfig config/tsconfig/oxlint.extensions.json extensions/memory-core/src/memory/manager.ts extensions/memory-core/src/memory/index.test.ts- passedgit diff --check origin/main...HEAD- passed