fix: resolve search, database, and docker bugs (#2079)

* fix: resolve search, database, and docker bugs (#1913, #1916, #1956, #1957, #2048)

- Fix concept/concepts param mismatch in SearchManager.normalizeParams (#1916)
- Add FTS5 keyword fallback when ChromaDB is unavailable (#1913, #2048)
- Add periodic WAL checkpoint and journal_size_limit to prevent unbounded WAL growth (#1956)
- Add periodic clearFailed() to purge stale pending_messages (#1957)
- Fix nounset-safe TTY_ARGS expansion in docker/claude-mem/run.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent silent data loss on non-XML responses, add queue info to /health (#1867, #1874)

- ResponseProcessor: mark messages as failed (with retry) instead of confirming
  when the LLM returns non-XML garbage (auth errors, rate limits) (#1874)
- Health endpoint: include activeSessions count for queue liveness monitoring (#1867)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: cache isFts5Available() at construction time

Addresses Greptile review: avoid DDL probe (CREATE + DROP) on every text
query. Result is now cached in _fts5Available at construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-19 22:19:18 -07:00
committed by GitHub
parent 8b6e61c70b
commit be99a5d690
11 changed files with 423 additions and 267 deletions
@@ -80,17 +80,31 @@ export async function processAgentResponse(
const summary = parseSummary(text, session.sessionDbId, summaryExpected);
if (
// Detect non-XML responses (auth errors, rate limits, garbled output).
// When the response contains no parseable XML and produced no observations,
// mark the pending messages as failed instead of confirming them — this prevents
// silent data loss when the LLM returns garbage (#1874).
const isNonXmlResponse = (
text.trim() &&
observations.length === 0 &&
!summary &&
!/<observation>|<summary>|<skip_summary\b/.test(text)
) {
);
if (isNonXmlResponse) {
const preview = text.length > 200 ? `${text.slice(0, 200)}...` : text;
logger.warn('PARSER', `${agentName} returned non-XML response; observation content was discarded`, {
logger.warn('PARSER', `${agentName} returned non-XML response; marking messages as failed for retry (#1874)`, {
sessionId: session.sessionDbId,
preview
});
// Mark messages as failed (retry logic in PendingMessageStore handles retries)
const pendingStore = sessionManager.getPendingMessageStore();
for (const messageId of session.processingMessageIds) {
pendingStore.markFailed(messageId);
}
session.processingMessageIds = [];
return;
}
// Convert nullable fields to empty strings for storeSummary (if summary exists)