Skip to content

Claude refresh times out on 0.7.10+: conflicting session ownership is rescanned once per subagent file #1239

Description

@sumeet-bansal

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

  1. Create a large valid JSONL session file containing two records with different
    ownerOrganizationUuid values.
  2. Create a few hundred subagent files under <session-id>/subagents/.
  3. Enable the Claude provider with an organization-scoped scan and refresh.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions