Quota guard aborts on a seven_day snapshot past its resetsAt (13.24.23), and the only workaround, restart, discards the in-memory queue
Before submitting
- I searched existing issues and confirmed this is not a duplicate
Related
- Defect 1 is a known bug, not a new one. The stale-snapshot mechanism is #3870 (closed 2026-09-11, consolidated into #3606). The
seven_dayvariant with an already-reset window is in the #3606 thread from @edgar-eaca (13.24.1), and @yhi264 reproduced the guard on 13.24.23 with/api/healthsnapshots. It is missing only from the #3606 plan text. The Defect 1 section below is one more 13.24.23 repro; please read it as corroboration of that thread. - Defect 2 is the part that is not tracked. Queue loss on restart: #3813 was consolidated into #3603 (port/liveness), and #3609 describes a DB-backed
PendingMessageStore. Neither matches the shipped build, where the observer queue lives only in memory. @yhi264 mentioned a discardedpendingCount=160in passing; the scale and the cause are below. Summary also posted in #3606: https://github.com/thedotmack/claude-mem/issues/3606#issuecomment-5651781960
Filing separately because Defect 2 is not covered by any open issue, and the only recovery for Defect 1 is what triggers it.
Bug description
Two defects that compound each other:
- The quota guard keeps aborting on a
seven_daysnapshot whoseresetsAtis already in the past. Memory capture stays off after the weekly window has reset, until the worker restarts. - The only workaround, a graceful worker restart, silently discards every queued observation. The queue lives only in process memory and the shutdown path neither drains nor persists it.
Environment
- claude-mem 13.24.23 (bun daemon
scripts/worker-service.cjs) - Claude Code CLI 2.1.268, Claude desktop app 1.52386.3
- macOS 14.5 (Darwin 23.5.0), Bun 1.3.11
- Provider
claude, auth: Claude Code OAuth token from keychain, subscription plan - Observer model
claude-haiku-4-5-20251001 - All times below are local, UTC+5
Defect 1: stale seven_day snapshot outlives resetsAt
GET /api/health at 23:07, same worker process (up since the previous day):
seven_day utilization=0.93 status=allowed_warning observedAt=18:24:14 resetsAt=19:00:00
five_hour status=allowed observedAt=22:53:24 resetsAt=03:20:00 (next day)
The weekly window reset at 19:00. Four hours later the guard still aborts on the entry observed at 18:24. Every cooldown probe gets a fresh five_hour event (allowed), then shouldAbortForQuota iterates all windows, hits the frozen seven_day entry and re-arms the 30 min cooldown:
[2026-09-12 22:52:49.757] [SESSION] Generator auto-starting (observation) using Claude SDK {queueDepth=150}
[2026-09-12 22:53:24.688] [SDK ] ← Response received (715 chars) ⚠️ **CRITICAL: Memory Recording Disabled** The claude-mem allowance has been exhausted (since 2026-09-12T16:47:10.757Z) ...
[2026-09-12 22:53:24.691] [PARSER ] SDK returned non-XML prose response
[2026-09-12 22:53:24.693] [SDK ] Aborting session for quota guard: quota:seven_day utilization 93.0% >= 93%
[2026-09-12 22:53:27.243] [SESSION] Generator paused for quota; preserving buffered work {pendingCount=122}
Every quota-guard abort in the log over two days reported exactly 93.0%, never a different value. Meanwhile the desktop app's own usage indicator had already dropped the weekly warning.
Corroborates the expired-window case already reported in #3606. Here the frozen entry's resetsAt was 4 hours in the past, the same shape edgar-eaca reported for seven_day on 13.24.1 (window reset about 26 h earlier) and yhi264 for five_hour on 13.24.23. Together with andreiiliuta's case on #3870, where resetsAt was still in the future, this is one more sign that neither check is enough alone: expire on resetsAt < now with resetsAt normalised once in set() (a seconds-valued resetsAt would otherwise switch the guard off, as edgar-eaca found), and refresh sibling windows from unifiedWindows, which also has to clear a frozen status: "rejected" (yhi264's run 2).
State of main (src/services/worker/RateLimitStore.ts, last change 2026-09-11 for #3903): unifiedWindows 0 occurrences, .delete( / .clear( 0 occurrences, observedAt written but never read. The bundled 13.24.23 matches: one set, no eviction, and the threshold table {five_hour:.95, seven_day_opus:.93, seven_day_sonnet:.92, seven_day:.93, overage:.95} is compared without any resetsAt or age check.
Side note, still present on 13.24.23 (#3902 family): the 715-char response above is the observer paraphrasing claude-mem's own health banner, which it was fed. The parser drops it as prose, so even a probe that passes the guard wastes its first turn.
Defect 2: restart discards the in-memory queue
Last status broadcast before the restart, then the whole shutdown sequence:
[2026-09-12 23:18:51.133] [WORKER ] Broadcasting processing status {isProcessing=true, queueDepth=1297, activeSessions=11}
[2026-09-12 23:18:51.698] [SYSTEM ] Shutdown initiated
[2026-09-12 23:18:51.707] [SHUTDOWN] Stopping Chroma MCP connection...
[2026-09-12 23:18:53.886] [SYSTEM ] Worker shutdown complete
No drain, no persist step. Evidence that the 1297 queued events are gone rather than recovered:
- Last
ENQUEUEDon the old worker wasmessageId=3562; the first one on the new worker ismessageId=4, so the id counter is per-process. pending_messageshas 0 rows after the restart, and the 13.24.23 bundle contains noINSERT ... INTO pending_messagesat all (case-insensitive search), so nothing writes queued work to SQLite.- The new worker's startup log has no recovery lines, only
Discarding stale memory_session_id from previous worker instance (Issue #817). - Between the last successful
STORED(21:47:08) and the shutdown (23:18:51) there were 602ENQUEUEDevents across 6 sessions in 5 projects. None of them produced observations.
Raw transcripts still exist, but there is no replay path: POST /api/import accepts ready observations, and transcript-watcher only tails new lines.
Steps to reproduce
- Run the observer on a subscription account until a
rate_limit_eventwithrateLimitType: seven_dayandutilization >= 0.93arrives. The guard aborts correctly. - Wait past that event's
resetsAtwhile the same worker process keeps running. - After each 30 min cooldown the probe is admitted, receives only a
five_hourevent and aborts again onquota:seven_day utilization 93.0%./api/healthshowsseven_day.resetsAt < now. - Restart the worker (
worker-service.cjs restart):rateLimitsbecomes{}, the first probe after the persisted cooldown succeeds, and every event queued during the outage is lost.
Expected behavior
- An entry whose
resetsAthas passed is ignored by the guard (normalise seconds vs ms once inset(), see the #3870 thread), and sibling windows are refreshed fromunifiedWindows. - Queued observer work survives a graceful restart: drain with a bounded timeout, or persist to SQLite and re-enqueue on boot.
- The SessionStart banner stops telling users that restarting will not help. With the cooldown persisted to
quota-cooldown.json, a restart is safe and is currently the only fix for Defect 1, but because of Defect 2 it costs the backlog.
Workaround
worker-service.cjs restart after confirming /api/health shows an expired seven_day.resetsAt. Result here: Worker restart verified (pid: 53114, version: 13.24.23), rateLimits: {}, first probe at 23:23:32, 22 observations stored within 6 minutes, 0 quota-guard aborts. The 1297-event backlog was lost.
Source: thedotmack/claude-mem