#9149: Fix: Allow QMD backend to work without OpenAI auth
stale
Cluster:
Memory Management Enhancements
Fixes #9143
## Problem
When QMD memory backend is configured, the system incorrectly requires OpenAI authentication even though QMD is fully local. This happens because:
1. QMD is wrapped in a `FallbackMemoryManager` with builtin index as fallback
2. When QMD fails for any reason, it tries to fall back to `MemoryIndexManager`
3. `MemoryIndexManager` requires OpenAI/Gemini auth for embeddings
4. This causes the entire memory system to fail even though QMD is configured
Users experienced:
- Gateway startup failures (in some cases)
- First QMD query works, second query fails with OpenAI 401 errors
- Memory search permanently disabled after first failure
## Root Cause Analysis
The issue occurs in the `FallbackMemoryManager` class when the primary (QMD) fails:
- Line 94: `const fallback = await this.ensureFallback();`
- The `ensureFallback()` method calls the fallback factory (line 183)
- The fallback factory calls `MemoryIndexManager.get(params)` (line 43)
- `MemoryIndexManager.get()` calls `createEmbeddingProvider()` which requires OpenAI/Gemini auth
- This throws an error like "No API key found for provider: openai"
- The error propagates up, causing memory search to fail completely
## Solution
Modified `FallbackMemoryManager.ensureFallback()` to catch and handle errors from the fallback factory gracefully:
```typescript
private async ensureFallback(): Promise<MemorySearchManager | null> {
if (this.fallback) {
return this.fallback;
}
try {
const fallback = await this.deps.fallbackFactory();
if (!fallback) {
log.warn("memory fallback requested but builtin index is unavailable");
return null;
}
this.fallback = fallback;
return this.fallback;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
log.warn(\`memory fallback unavailable: \${message}\`);
return null;
}
}
```
This allows QMD to work independently without requiring OpenAI auth. If QMD fails and the fallback is also unavailable, the original QMD error is thrown (which is more helpful than an OpenAI auth error).
## Changes
- **src/memory/search-manager.ts**: Added try-catch in `ensureFallback()` method
- **src/memory/search-manager.test.ts**: Added test case for fallback error handling scenario
## Testing
Added a comprehensive test case that verifies:
1. QMD manager is created successfully
2. When QMD search fails, it attempts to create a fallback
3. When fallback creation fails (e.g., missing OpenAI auth), the error is handled gracefully
4. The original QMD error is thrown (not the auth error)
## Impact
✅ **Fixes the reported issue**: QMD backend now works without OpenAI auth as intended
✅ **Improves fallback resilience**: Fallback errors are handled gracefully instead of crashing
✅ **Better error messages**: Users see QMD-specific errors instead of confusing auth errors
✅ **Enables offline deployments**: QMD can be used in airgapped environments without external API dependencies
✅ **Minimal code changes**: Only 2 files modified, focused fix with low risk
✅ **Backward compatible**: No breaking changes, existing behavior preserved when fallback works
## Why This Improves the Code
- **Separation of concerns**: QMD backend failure shouldn't be masked by unrelated auth failures
- **Graceful degradation**: System handles missing dependencies elegantly rather than crashing
- **Better UX**: Clear error messages help users understand the actual problem
- **Follows intent**: QMD is advertised as "fully local" - it should work without external API keys
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR updates the QMD-backed `FallbackMemoryManager` so that if the fallback factory (builtin index) throws (e.g., due to missing OpenAI/Gemini auth for embeddings), the error is caught and treated as “fallback unavailable” rather than disabling memory search entirely. It also adds a unit test that simulates QMD search failing and builtin index creation failing, asserting the original QMD error is surfaced.
<h3>Confidence Score: 4/5</h3>
- This PR is likely safe to merge, but the new test can be order-dependent due to a module-level cache not being reset.
- The production change is narrowly scoped to catching fallbackFactory errors in `ensureFallback()` and returning null, which matches the intended behavior and prevents auth errors from masking QMD failures. The main concern is test reliability: `QMD_MANAGER_CACHE` persists across tests and can cause the new test to reuse a previously cached manager, skipping the fallback path and making assertions flaky.
- src/memory/search-manager.test.ts (test isolation vs QMD_MANAGER_CACHE)
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#11179: fix(memory): replace confusing "No API key" errors in memory tools ...
by liuxiaopai-ai · 2026-02-07
86.6%
#9624: fix(memory): resolve QMD search returning empty results [AI-assisted]
by kowshik24 · 2026-02-05
84.9%
#20125: fix(doctor): skip memorySearch provider check when using QMD backend
by brandonwise · 2026-02-18
83.0%
#21471: fix: check QMD backend before memory search config
by lbo728 · 2026-02-20
82.0%
#11364: fix(memory/qmd): prevent cascading failure when query fails or retu...
by blazerui · 2026-02-07
82.0%
#10801: fix: eagerly initialize QMD memory backend on gateway startup
by 1kuna · 2026-02-07
80.7%
#17660: fix: skip embedding provider check in doctor when QMD backend is co...
by echoVic · 2026-02-16
80.0%
#22937: fix: remove legacy unsuffixed QMD collections on upgrade
by sud0n1m-ziggy · 2026-02-21
79.7%
#16917: fix(memory): close stale SQLite connection after qmd update
by zerone0x · 2026-02-15
79.2%
#9381: Fix: Allow QMD CLI memory search when scope is restrictive
by vishaltandale00 · 2026-02-05
79.0%