fix(host-service): stop usage.history throwing on very large usage histories

Merged
SA
2mo ago
+6-1

Problem

usage.history (and anything else routed through collectUsageEntries) fails deterministically on machines with a very large Claude usage history: the usage worker throws RangeError: Maximum call stack size exceeded, so the Usage tab's history and leaderboard data never load — every time, for exactly the heaviest users. 3 events on release 1.25.0 (HOST-SERVICE-4V), all the same machines retrying.

User-visible harm: heavy Claude users open the Usage tab and its history/leaderboard never loads, on every attempt.

Reach: the defect shipped in the leaderboard work (#6875, release 1.25.0) and is unchanged on main. This fix rides the next host-service/desktop release and reaches affected users via the normal update path; the failure is deterministic per machine, so the fix verifiably removes it for them.

Why code: archiving does nothing — the feature is broken for those users, and the failure recurs on every query. A Sentry-side filter is forbidden by the classification contract (#6164) and wouldn't restore the feature. The one-line root-cause fix wins.

Root cause

packages/host-service/src/trpc/router/usage/history/entries.ts:83:

entries.push(...claudeEntriesByMessage.values());

Argument spread passes every deduped Claude entry as a distinct call argument. V8 bounds call arguments by stack size, so past roughly 100–125k entries the call itself throws RangeError: Maximum call stack size exceeded. A heavy user's 30-day window can exceed that (one entry per assistant API message). Reproduced under Node 22 (the worker's engine) with a 200k-entry map:

$ node spread-repro.mjs
spread: RangeError: Maximum call stack size exceeded
loop: ok, 200000 entries

(Bun/JSC tolerates 200k, which is why local bun test never caught it — production host-worker runs on V8.)

Fix

Append in a loop — stack usage no longer scales with entry count:

for (const entry of claudeEntriesByMessage.values()) {
	entries.push(entry);
}

Worker-boundary payload does NOT need bounding: I verified what actually crosses the MessagePort. computeUsageHistory returns UsageHistory, already a bounded aggregate — day buckets bounded by the days param, per-model rows bounded by distinct models, projectDetails capped at top 24 projects with top 20 sessions each (aggregate.ts). computeLeaderboardPayload returns day×provider×model buckets, likewise bounded. The unbounded entries array is internal to the worker and never serialized, so the spread is the whole defect and this is the minimal fix.

No typed cause added: this fix removes a throw rather than adding one — the query now succeeds. Nothing reads a cause.kind here, so no TRPCError machinery was introduced.

Adjacent, deliberately untouched: parseCodexLogFile pushes entries one at a time already (no spread); the array-literal spreads in aggregate.ts ([...map.values()]) iterate rather than pass call arguments and have no argument-count limit; the desktop minidump OOM issues (DESKTOP-12V et al.) are a separate memory-scaling face of this subsystem owned by the triage automation and are not addressed here.

Verification

$ bunx biome check packages/host-service/src/trpc/router/usage/history/entries.ts
Checked 1 file in 9ms. No fixes applied.

$ cd packages/host-service && bun run typecheck
$ tsc --noEmit --emitDeclarationOnly false
(exit 0)

$ bun run test src/trpc/router/usage/history/
 20 pass
 0 fail
 28 expect() calls
Ran 20 tests across 2 files. [31.00ms]

Refs HOST-SERVICE-4V

https://claude.ai/code/session_0133xN5zZ3goUNfKAy9cLfA9


Summary by cubic

Fixes usage.history failing with RangeError: Maximum call stack size exceeded for users with very large Claude usage histories, so the Usage tab history and leaderboard now load for heavy users. The deduped entries are appended one at a time instead of spread into push(), which passed every entry as a call argument and hit V8's argument limit. No payload changes are needed since the worker-boundary data is already bounded. Refs HOST-SERVICE-4V.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue that could prevent large usage histories from loading successfully.
    • Usage history entries are now processed reliably, avoiding failures when many Claude entries are present.