Memory/QMD: reuse default model cache and skip ENOENT warnings#12114
Memory/QMD: reuse default model cache and skip ENOENT warnings#12114
Conversation
| try { | ||
| await fs.symlink(defaultModelsDir, targetModelsDir, "dir"); | ||
| } catch (symlinkErr: unknown) { | ||
| const code = (symlinkErr as NodeJS.ErrnoException).code; | ||
| if (process.platform === "win32" && (code === "EPERM" || code === "ENOTSUP")) { | ||
| await fs.symlink(defaultModelsDir, targetModelsDir, "junction"); | ||
| } else { |
There was a problem hiding this comment.
Symlink parent dir missing
symlinkSharedModels() tries to create targetModelsDir directly, but .../xdg-cache/qmd/models’s parent directory (.../xdg-cache/qmd) is not created anywhere. Since initialize() only does mkdir(this.xdgCacheHome) and mkdir(path.dirname(this.indexPath)) (which also resolves to .../xdg-cache/qmd only if index.sqlite is created first), fs.symlink() will throw ENOENT on a fresh state dir and you’ll just warn + fall back to re-downloading. Create path.dirname(targetModelsDir) before calling symlink so the intended sharing works on first run.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/qmd-manager.ts
Line: 521:527
Comment:
**Symlink parent dir missing**
`symlinkSharedModels()` tries to create `targetModelsDir` directly, but `.../xdg-cache/qmd/models`’s parent directory (`.../xdg-cache/qmd`) is not created anywhere. Since `initialize()` only does `mkdir(this.xdgCacheHome)` and `mkdir(path.dirname(this.indexPath))` (which also resolves to `.../xdg-cache/qmd` only if `index.sqlite` is created first), `fs.symlink()` will throw `ENOENT` on a fresh state dir and you’ll just warn + fall back to re-downloading. Create `path.dirname(targetModelsDir)` before calling `symlink` so the intended sharing works on first run.
How can I resolve this? If you propose a fix, please make it concise.| it("does not overwrite existing models directory", async () => { | ||
| // Pre-create the custom models dir with different content. | ||
| await fs.mkdir(customModelsDir, { recursive: true }); | ||
| await fs.writeFile(path.join(customModelsDir, "custom-model.bin"), "custom"); | ||
|
|
There was a problem hiding this comment.
Test can be flaky
In "does not overwrite existing models directory", you pre-create customModelsDir before QmdMemoryManager.create(), but initialize() still runs symlinkSharedModels() and may log a warn if the default models dir exists yet the symlink creation fails for unrelated reasons (e.g., permissions on platforms where directory symlinks aren’t available). The test doesn’t assert on logWarnMock, so it can intermittently pass while the feature is effectively broken. Consider asserting no warn occurred in this test as well, or explicitly simulating the platform behavior you expect.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/memory/qmd-manager.test.ts
Line: 652:656
Comment:
**Test can be flaky**
In "does not overwrite existing models directory", you pre-create `customModelsDir` before `QmdMemoryManager.create()`, but `initialize()` still runs `symlinkSharedModels()` and may log a warn if the default models dir exists yet the symlink creation fails for unrelated reasons (e.g., permissions on platforms where directory symlinks aren’t available). The test doesn’t assert on `logWarnMock`, so it can intermittently pass while the feature is effectively broken. Consider asserting no warn occurred in this test as well, or explicitly simulating the platform behavior you expect.
How can I resolve this? If you propose a fix, please make it concise.de85d2f to
a9e48cd
Compare
a9e48cd to
65241af
Compare
65241af to
af6f1ac
Compare
af6f1ac to
23dad73
Compare
QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist.
23dad73 to
efa35a6
Compare
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
Integrated upstream improvements: - CRITICAL: Fix bundled hooks broken since 2026.2.2 (openclaw#9295) - Grok web search provider (xAI) with inline citations - Telegram video note support with tests and docs - QMD model cache sharing optimization (openclaw#12114) - Context overflow false positive fix (openclaw#2078) - Model failover 400 status handling (openclaw#1879) - Dynamic config loading per-message (openclaw#11372) - Gateway post-compaction amnesia fix (openclaw#12283) - Skills watcher: ignore Python venvs and caches - Telegram send recovery from stale thread IDs - Cron job parameter recovery (openclaw#12124) - Auto-reply weekday timestamps (openclaw#12438) - Utility consolidation refactoring (PNG, JSON, errors) - Cross-platform test normalization (openclaw#12212) - macOS Nix defaults support (openclaw#12205) Preserved DEV enhancements: - Docker multi-stage build with enhanced tooling (gh, gog, obsidian-cli, uv, nano-pdf, mcporter, qmd) - Comprehensive .env.example documentation (371 lines) - Multi-environment docker-compose support (DEV/PRD) - GOG/Tailscale integration - Fork-sync and openclaw-docs skills - UI config editor (Svelte) - Fork workflow documentation Merge strategy: Cherry-picked 22 upstream commits, preserved DEV Docker architecture. Docker files unchanged: Dockerfile, docker-compose.yml, docker-setup.sh, .env.example Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
- Symlink default model cache into custom XDG_CACHE_HOME per agent - Skip ENOENT silently when default models directory is missing - Clean up compaction test imports - Add changelog entry for QMD cache reuse
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
…law#12114) * Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (openclaw#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (openclaw#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (openclaw#12114) (thanks @tyler6204)
* Memory/QMD: symlink default model cache into custom XDG_CACHE_HOME QmdMemoryManager overrides XDG_CACHE_HOME to isolate the qmd index per-agent, but this also moves where qmd looks for its ML models (~2.1GB). Since models are installed at the default location (~/.cache/qmd/models/), every qmd invocation would attempt to re-download them from HuggingFace and time out. Fix: on initialization, symlink ~/.cache/qmd/models/ into the custom XDG_CACHE_HOME path so the index stays isolated per-agent while the shared models are reused. The symlink is only created when the default models directory exists and the target path does not already exist. Includes tests for the three key scenarios: symlink creation, existing directory preservation, and graceful skip when no default models exist. * Memory/QMD: skip model symlink warning on ENOENT * test: stabilize warning-filter visibility assertion (#12114) (thanks @tyler6204) * fix: add changelog entry for QMD cache reuse (#12114) (thanks @tyler6204) * fix: handle plain context-overflow strings in compaction detection (#12114) (thanks @tyler6204)
Summary
XDG_CACHE_HOMEso preinstalled models are reused instead of re-downloadedENOENT) as an expected first-run no-op instead of logging a warningTesting
pnpm tsgopnpm exec vitest run src/memory/qmd-manager.test.tsGreptile Overview
Greptile Summary
This PR adds a
symlinkSharedModels()step toQmdMemoryManager.initialize()so per-agentXDG_CACHE_HOMEdirectories can reuse a shared/default QMD models cache (avoiding repeated model downloads). It also extendsqmd-managertests to cover symlink creation, preserving an existing target directory, and treating missing default models (ENOENT) as a silent no-op.Key area to double-check is the first-run behavior of symlink creation: the implementation currently relies on the target directory structure existing before
fs.symlink()is called, and if it doesn’t, the code will warn and fall back to re-download behavior.Confidence Score: 3/5
symlinkSharedModels()appears to attempt to create the symlink before ensuring the target parent path exists, which would reliably throw ENOENT on first run and negate the intended behavior (falling back to downloads). Tests also don’t fully guard against silently falling back in some scenarios.