#4068·claude-mem

Quota guard aborts on a seven_day snapshot past its resetsAt (13.24.23), and the only workaround, restart, discards the in-memory queue

Author: mashuroffCreated Sep 12, 2026Updated Sep 17, 2026

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_day variant 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/health snapshots. 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 discarded pendingCount=160 in 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:

  1. The quota guard keeps aborting on a seven_day snapshot whose resetsAt is already in the past. Memory capture stays off after the weekly window has reset, until the worker restarts.
  2. 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 ENQUEUED on the old worker was messageId=3562; the first one on the new worker is messageId=4, so the id counter is per-process.
  • pending_messages has 0 rows after the restart, and the 13.24.23 bundle contains no INSERT ... INTO pending_messages at 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 602 ENQUEUED events 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

  1. Run the observer on a subscription account until a rate_limit_event with rateLimitType: seven_day and utilization >= 0.93 arrives. The guard aborts correctly.
  2. Wait past that event's resetsAt while the same worker process keeps running.
  3. After each 30 min cooldown the probe is admitted, receives only a five_hour event and aborts again on quota:seven_day utilization 93.0%. /api/health shows seven_day.resetsAt < now.
  4. Restart the worker (worker-service.cjs restart): rateLimits becomes {}, the first probe after the persisted cooldown succeeds, and every event queued during the outage is lost.

Expected behavior

  • An entry whose resetsAt has passed is ignored by the guard (normalise seconds vs ms once in set(), see the #3870 thread), and sibling windows are refreshed from unifiedWindows.
  • 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.