#16917: fix(memory): close stale SQLite connection after qmd update
stale
size: XS
experienced-contributor
Cluster:
QMD Memory Management Fixes
## Summary
- **Problem:** `QmdMemoryManager` caches a single read-only SQLite connection (`this.db`) for the process lifetime. In WAL mode, this stale connection misses rows written by the `qmd update` subprocess, causing `resolveDocLocation()` to silently drop ~50% of search results over time.
- **Why it matters:** Users lose valid memory search results with no error surfaced — queries return fewer results than expected until gateway restart.
- **What changed:** After `runUpdate()` completes, the cached SQLite connection is now closed and nulled, forcing `ensureDb()` to open a fresh connection on the next query that sees the latest WAL checkpoint.
- **What did NOT change:** No changes to search logic, update scheduling, or session export. The `close()` lifecycle method already used this same pattern.
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [x] Memory / storage
- [ ] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #16844
## User-visible / Behavior Changes
Memory search results are no longer silently dropped after `qmd update` writes new document hashes. Previously, results degraded over time until gateway restart.
## 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
## Repro + Verification
### Environment
- OS: macOS / Linux
- Runtime: Node.js
- Model/provider: any
- Integration/channel: any with QMD memory
### Steps
1. Start OpenClaw with QMD memory backend
2. Add/modify files in memory directory
3. Wait for `qmd update` + `qmd embed` to run
4. Run `memory_search` for content in the updated files
5. Previously: some results silently dropped. Now: all results returned.
### Expected
All indexed documents found by QMD search are resolved and returned.
### Actual (before fix)
~50% of results silently dropped because `resolveDocLocation()` queried a stale SQLite snapshot.
## Evidence
- [x] Failing test/log before + passing after
New test: `closes cached sqlite connection after update so WAL readers see new rows` — verifies `db.close()` is called and `this.db` is nulled after sync.
## Human Verification (required)
- Verified scenarios: All 34 existing + 1 new qmd-manager tests pass
- Edge cases checked: `close()` already uses the same `db.close(); db = null` pattern (line 493-496)
- What I did **not** verify: Live WAL-mode SQLite with real QMD subprocess (requires full environment)
## Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Revert the 4-line addition in `runUpdate()`
- Files/config to restore: `src/memory/qmd-manager.ts`
- Known bad symptoms: If the close/reopen introduces performance overhead, it would manifest as slightly slower first search after each update cycle
## Risks and Mitigations
- Risk: Reopening the SQLite connection after every update adds minor overhead
- Mitigation: `ensureDb()` is lightweight (opens read-only + sets PRAGMA busy_timeout = 1), and updates run infrequently (configurable interval, default debounce 60s)
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a stale SQLite connection bug in `QmdMemoryManager` where the cached read-only connection (`this.db`) would miss rows written by the `qmd update` subprocess in WAL mode, causing `resolveDocLocation()` to silently drop search results.
- **Bug fix in `runUpdate()`**: After `qmd update` and `qmd embed` complete, the cached SQLite connection is now closed and nulled (`this.db.close(); this.db = null;`), forcing `ensureDb()` to open a fresh connection on the next query.
- **Pattern consistency**: The fix reuses the exact same close-and-null pattern already present in the `close()` lifecycle method (lines 493-496).
- **Placement**: The connection reset is placed after `this.docPathCache.clear()`, ensuring both the in-memory path cache and the stale DB handle are invalidated together.
- **Test coverage**: A new test verifies `db.close()` is called once and `this.db` is nulled after sync, following established test patterns in the file.
- **Concurrency safety**: No risk of closing a connection mid-query — `DatabaseSync` operations are synchronous, so `db.close()` can only execute between async yield points in the search loop, and `ensureDb()` will recreate the connection if needed.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it's a minimal, well-scoped bug fix that reuses an existing pattern.
- The change is 4 lines of production code that exactly mirrors an existing pattern in `close()`. The fix addresses a real WAL-mode stale snapshot problem. `DatabaseSync` is synchronous, eliminating concurrency concerns. The new test is focused and follows existing conventions. No behavioral changes to search logic, update scheduling, or session export.
- No files require special attention.
<sub>Last reviewed commit: 6e1d0a1</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#9624: fix(memory): resolve QMD search returning empty results [AI-assisted]
by kowshik24 · 2026-02-05
82.8%
#17657: fix: clear QMD manager cache on in-process restart (SIGUSR1)
by IrriVisionTechnologies · 2026-02-16
79.8%
#20085: Fix QMD memory_search empty results when docid key changes
by rylena · 2026-02-18
79.7%
#20125: fix(doctor): skip memorySearch provider check when using QMD backend
by brandonwise · 2026-02-18
79.6%
#21471: fix: check QMD backend before memory search config
by lbo728 · 2026-02-20
79.4%
#9149: Fix: Allow QMD backend to work without OpenAI auth
by vishaltandale00 · 2026-02-04
79.2%
#16968: fix(qmd): per-collection search to prevent large collections drowni...
by ProgramCaiCai · 2026-02-15
79.0%
#19179: fix(memory): respect readonly mode in database initialization
by marliechorgan · 2026-02-17
78.6%
#11364: fix(memory/qmd): prevent cascading failure when query fails or retu...
by blazerui · 2026-02-07
78.1%
#15620: fix(memory): await sync in search to prevent database closure race
by superlowburn · 2026-02-13
77.9%