← Back to PRs

#16917: fix(memory): close stale SQLite connection after qmd update

by zerone0x open 2026-02-15 07:35 View on GitHub →
stale size: XS experienced-contributor
## 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