Description
Since 0.7.10 the Claude provider fails every refresh on this Mac with claude timed out after 120s (33 consecutive cycles on 0.7.11). Codex and Cursor refresh in 1 to 3 s, and
GET /api/oauth/usage with the same keychain credential answers in about 0.1 s, but the Claude
card never publishes because the timed-out snapshot is discarded whole.
The cost is amplification, not file size: the same parent session file is re-read once per
subagent file on every refresh. Three defects combine.
- In
sessionIdentity, a session file whose owner records disagree takes the conflict branch
(if let owner, owner != candidate { return nil }) and returns before sessionOwnership[...]
is written, so the conflict is never cached.
- In
ownedUsageFiles, every subagent file resolves to its parent session and calls
sessionIdentity on it again, and seenPaths dedupes on the child path rather than the
resolved parent.
Data.split materializes the whole line array before the loop body runs, so the early return
saves nothing.
A 4 s process sample during a refresh puts essentially all provider-worker time in
ClaudeLogUsageScanner.ownedUsageFiles -> sessionIdentity, mostly inside Data.split. The main
thread is idle.
On this archive (about 11.7k JSONL files, 8.1 GB; 1,876 session files totalling 2.77 GB and
9,845 subagent files; 4.1 GB modified in the last 30 days) 21 session files carry conflicting
owner UUIDs because they were resumed under a second Claude login. Counting one parent read per
subagent child gives, per ownership pass:
70.9 MB x 363 reads = 25.15 GB (session with 362 subagent files)
44.6 MB x 421 reads = 18.34 GB
54.3 MB x 240 reads = 12.72 GB
60.6 MB x 61 reads = 3.61 GB
... 17 more conflicted sessions
= 62.65 GB of Data.split per refresh from conflicted files, + 2.46 GB for everything else
That is a code-derived work count, not measured throughput. Even so, the conflicted files alone
blow the 120 s deadline: at the 295 MB/s that Data.split reaches in a Swift 5.10 microbenchmark
on one of these files, 65 GB is 221 s. The ownership pass also runs over every discovered file
before the parser's 30-day window, and has no cancellation check inside a file.
0.7.9, which has no ownership pass, refreshes Claude on the same archive in 21.6 s cold and
about 2 s warm.
Steps to Reproduce
- Create a large valid JSONL session file containing two records with different
ownerOrganizationUuid values.
- Create a few hundred subagent files under
<session-id>/subagents/.
- Enable the Claude provider with an organization-scoped scan and refresh.
- Count physical identity reads of the parent: one per subagent child, every refresh, with the
files unchanged.
Whether the fixture alone reaches 120 s depends on its size and hardware; the timeout above was
observed on the full archive.
Expected Behavior
An unchanged parent's ownership, including a conflict verdict, is evaluated once per pass, reused
for all its subagent files, and cached across passes by the existing path/size/mtime key. Claude
refresh completes and publishes quota.
Actual Behavior
The conflicting parent is re-read for every subagent file on every refresh. On this Mac the
ownership phase exhausts the provider deadline every cycle, so the Claude card stays stale and
the process burns a core for two of every five minutes.
OpenUsage Version
0.7.11 (also 0.7.10; not present on 0.7.9)
macOS Version
26.6.2
Mac Type
Apple Silicon
Additional Context
A scoped fix that keeps attribution unchanged:
- Memoize identity by resolved parent path in
ownedUsageFiles.
- Make
sessionIdentity's result three-state (owned / unattributed / conflicted) so a conflict
can be cached, since (nil, nil) collides with "unattributed", which is kept under
allowsUnattributedSessions while conflicts are dropped.
- Cache read failures for the current pass only.
- Check
Task.isCancelled every few megabytes inside the file loop.
Two things to avoid:
- Applying the 30-day window to the identity pass: an in-window subagent whose parent is older
than 30 days would lose its parent and be dropped.
- Stopping at the first owner, or reading a bounded prefix: owner fields appear only on
bridge-session records, first at line 534 in the sampled file, and first-owner-wins would
defeat conflict detection.
Related: #1223 covers the provider deadline discarding an already-fetched Codex quota while local
history runs. This report is the Claude-specific amplification and has its own reproducer.
Sanitized logs and the profile excerpt are available on request. No credentials or conversation
content are included.
Description
Since 0.7.10 the Claude provider fails every refresh on this Mac with
claude timed out after 120s(33 consecutive cycles on 0.7.11). Codex and Cursor refresh in 1 to 3 s, andGET /api/oauth/usagewith the same keychain credential answers in about 0.1 s, but the Claudecard never publishes because the timed-out snapshot is discarded whole.
The cost is amplification, not file size: the same parent session file is re-read once per
subagent file on every refresh. Three defects combine.
sessionIdentity, a session file whose owner records disagree takes the conflict branch(
if let owner, owner != candidate { return nil }) and returns beforesessionOwnership[...]is written, so the conflict is never cached.
ownedUsageFiles, every subagent file resolves to its parent session and callssessionIdentityon it again, andseenPathsdedupes on the child path rather than theresolved parent.
Data.splitmaterializes the whole line array before the loop body runs, so the early returnsaves nothing.
A 4 s process sample during a refresh puts essentially all provider-worker time in
ClaudeLogUsageScanner.ownedUsageFiles -> sessionIdentity, mostly insideData.split. The mainthread is idle.
On this archive (about 11.7k JSONL files, 8.1 GB; 1,876 session files totalling 2.77 GB and
9,845 subagent files; 4.1 GB modified in the last 30 days) 21 session files carry conflicting
owner UUIDs because they were resumed under a second Claude login. Counting one parent read per
subagent child gives, per ownership pass:
That is a code-derived work count, not measured throughput. Even so, the conflicted files alone
blow the 120 s deadline: at the 295 MB/s that
Data.splitreaches in a Swift 5.10 microbenchmarkon one of these files, 65 GB is 221 s. The ownership pass also runs over every discovered file
before the parser's 30-day window, and has no cancellation check inside a file.
0.7.9, which has no ownership pass, refreshes Claude on the same archive in 21.6 s cold and
about 2 s warm.
Steps to Reproduce
ownerOrganizationUuidvalues.<session-id>/subagents/.files unchanged.
Whether the fixture alone reaches 120 s depends on its size and hardware; the timeout above was
observed on the full archive.
Expected Behavior
An unchanged parent's ownership, including a conflict verdict, is evaluated once per pass, reused
for all its subagent files, and cached across passes by the existing path/size/mtime key. Claude
refresh completes and publishes quota.
Actual Behavior
The conflicting parent is re-read for every subagent file on every refresh. On this Mac the
ownership phase exhausts the provider deadline every cycle, so the Claude card stays stale and
the process burns a core for two of every five minutes.
OpenUsage Version
0.7.11 (also 0.7.10; not present on 0.7.9)
macOS Version
26.6.2
Mac Type
Apple Silicon
Additional Context
A scoped fix that keeps attribution unchanged:
ownedUsageFiles.sessionIdentity's result three-state (owned / unattributed / conflicted) so a conflictcan be cached, since
(nil, nil)collides with "unattributed", which is kept underallowsUnattributedSessionswhile conflicts are dropped.Task.isCancelledevery few megabytes inside the file loop.Two things to avoid:
than 30 days would lose its parent and be dropped.
bridge-sessionrecords, first at line 534 in the sampled file, and first-owner-wins woulddefeat conflict detection.
Related: #1223 covers the provider deadline discarding an already-fetched Codex quota while local
history runs. This report is the Claude-specific amplification and has its own reproducer.
Sanitized logs and the profile excerpt are available on request. No credentials or conversation
content are included.