Stale `quota_exhausted` banner at SessionStart days after the quota recovered
Summary
After a quota_exhausted outage, the SessionStart context keeps emitting the full
"claude-mem can't save memories right now" banner for days after the quota has
actually recovered. The banner also instructs the assistant to open its reply with the
outage and explicitly tells it not to restart the worker — so a new session starts
with an alarming, factually wrong report of a total memory outage while the worker is,
in fact, storing observations normally.
In my case the banner claimed memory had been dead since 2026-09-11T12:48Z, and the
worker wrote observations 36018 and 36019 four minutes later in the same session.
Environment
- claude-mem
13.24.23(marketplacethedotmack, confirmed via the.in_usemarker) - Claude Code on Windows 11 Pro (10.0.26100)
CLAUDE_MEM_PROVIDER=claude,CLAUDE_MEM_CLAUDE_AUTH_METHOD=subscriptionCLAUDE_MEM_MODEL=claude-haiku-4-5-20251001CLAUDE_MEM_RUNTIME=worker
Steps to reproduce
- Run the observer on
provider: claude/auth: subscriptionuntil the subscription allowance is exhausted (lastErrorKind: "quota_exhausted",consecutiveFailures >= 3). - Stop using Claude Code long enough for the allowance to reset (hours or days).
- Start a new session.
Expected: no banner, or at most a soft note that a past outage may have ended.
Actual: the full outage banner, stating the outage as a present, verified fact.
Evidence
~/.claude-mem/observer-health.json, read immediately after the banner was shown:
{
"consecutiveFailures": 0,
"failingSinceAt": null,
"lastErrorAt": 1789141741951,
"lastErrorMessage": "Provider reported the inference allowance exhausted",
"lastErrorProvider": "claude",
"lastSuccessAt": 1789366825424,
"quotaCooldown": null,
"lastErrorKind": "quota_exhausted"
}
lastErrorAt= 2026-09-11 17:49 locallastSuccessAt= 2026-09-14 08:20 local — ~63 hours later- The session started at 08:18. The banner was rendered at 08:18; the first successful observation landed at 08:20.
Worker log from the very same session that displayed the banner:
[2026-09-14 08:22:06.750] [DB] STORED | sessionDbId=918 | obsCount=1 | obsIds=[36018]
[2026-09-14 08:22:16.924] [DB] STORED | sessionDbId=918 | obsCount=1 | obsIds=[36019]
Root cause
The health gate itself is correct — from scripts/context-generator.cjs (minified):
// isUnhealthy
function bt(t){ return t!==null && t.consecutiveFailures>=3 && (t.lastErrorAt??0)>(t.lastSuccessAt??0) }
// warning selection
function Gt(t=!1){ let e=It(), r=null; return bt(e)?r=wt(e):xt(e)&&(r=$t(e)), r?(t?jr(r):r):"" }
The problem is staleness, not the predicate. observer-health.json only heals as a
side effect of the next successful generation, and that success cannot happen until
traffic arrives — which is after the SessionStart hook has already read the file and
rendered the banner. So the first session following any recovered outage is guaranteed
to render a stale state, no matter how long ago the outage ended.
Nothing age-checks lastErrorAt. A 63-hour-old error is presented with exactly the same
confidence as a 30-second-old one, even though a Claude subscription allowance resets
every few hours — which makes an error that old almost certainly obsolete.
The wording compounds it. The banner asserts as verified present fact:
Until the allowance resets or you add capacity, nothing from this session — or any other — will be remembered.
and then:
(Assistant: tell the user about this outage at the very start of your first reply, quoting the error above. Do NOT restart the worker and do NOT suggest restarting it.)
So the assistant is directed to lead with a confident outage report that is false, and is pre-emptively discouraged from the one action a user would otherwise take to test it. The user is left believing their memory capture is dead while it is working fine.
Suggested fixes
Any one of these resolves it; the first is the cheap one.
- Age-gate the quota branch. If
Date.now() - lastErrorAtexceeds the provider's reset window (~5 h for a Claude subscription; make it configurable), suppress the banner or downgrade it to something like: "A quota outage was recorded N hours ago and has not been re-tested; memory capture may already have recovered." - Probe on recovery. Have the worker issue a cheap validation call on startup, or
whenever the health file shows a stale error, and update
observer-health.jsonbefore SessionStart consumes it. - Don't state unverified facts. "nothing ... will be remembered" should only be emitted for a failure observed recently. Otherwise report it as a last known state together with its age, and drop the instruction to open the reply with it.
Minor, spotted nearby: lastErrorKind is read by $r() but is missing from the
DEFAULT_HEALTH object (Ir), so it isn't normalised on load like every sibling field.
Source: thedotmack/claude-mem