Skip to content

Unified reports count OpenCode Claude Agent sessions twice #2004

Description

@igorovh

What happened?

OpenCode's claude-agent provider runs Claude Code under the hood. Each turn is saved twice:

  • in OpenCode's database (opencode.db, table session_message), with the model stored as {"id":"opus","providerID":"claude-agent"}
  • in Claude Code's own transcript, ~/.claude/projects/<project>/<session>.jsonl

The OpenCode row links to the transcript: each content part has native.sessionID, which matches the transcript's file name.

ccusage daily adds up both sources, so this usage is counted twice. One real day (2026-10-31):

Command Cache read Cost
ccusage claude daily 876.2M $319.96
ccusage opencode daily 1,061.3M (875.3M of it from claude-agent) $607.37
ccusage daily 1,937.4M $927.33

The OpenCode copy is also priced too high. OpenCode stores only the alias opus, and ccusage prices it as an older Opus: $573.68 for tokens that cost $319.37 as claude-opus-5-5 in the Claude transcript.

Steps to reproduce

  1. In OpenCode, use the claude-agent provider for a session.
  2. Run ccusage claude daily and ccusage opencode daily for that day.
  3. Run ccusage daily. Its total is the sum of both.

Expected behavior

Unified reports count each Claude Agent turn once.

Version

20.0.28

Activity

  1. pullfrog commented on Oct 31, 2026

    @pullfrog
    Contributor
  2. added
    bugSomething isn't working
    priority:highHigh user impact or an important regression.
    triage:maintainableFits the repository's maintainable scope and needs review.
    on Oct 31, 2026
  3. igorovh commented on Oct 31, 2026

    @igorovh
    ContributorAuthor

    I have a fix ready on my fork, but I can't open a PR yet: GitHub rejects createPullRequest for my account, which I guess is the contribution gate. Could you approve me with lgtm?

    Branch: main...igorovh:ccusage:fix/opencode-claude-agent-dedup

    What the fix does

    In unified reports, OpenCode v2 rows whose content[].native.sessionID matches a Claude Code transcript on disk are skipped, so the Claude source counts that usage once. ccusage opencode still shows every row. If the transcript is gone (for example after cleanupPeriodDays), the OpenCode rows are counted instead, so no usage disappears.

    For 2026-10-31, ccusage daily goes from $927.33 to $360.61. Cache read goes from 1,937.4M to 1,062.2M, which is exactly Claude plus the OpenCode usage that is not claude-agent.

    Why keep the Claude side

    I compared 35 real claude-agent sessions with their Claude transcripts:

    • Input and cache write match, and cache read matches within 0.08%.
    • Output looks about 38% lower in OpenCode, but OpenCode stores thinking separately in tokens.reasoning. For all 330 messages checked, output + reasoning equals Claude's output_tokens.
    • The 0.08% gap comes from interrupted steps. OpenCode sometimes saves them with tokens: null, while the Claude transcript has their usage (334,142 cache-read tokens in one case).
    • OpenCode stores only the alias opus. The resolved version is nowhere in its database or config. The Claude transcript records claude-opus-5-5.

    The rejected alternatives are in the PR description. If you'd prefer another approach, I'm happy to switch.

    Other problems I found while testing

    These are not fixed on this branch. I can open separate issues for them.

    1. ccusage opencode overprices claude-agent/opus. The alias has no pricing entry and resolves to an older Opus: $573.68 vs $319.37 for the same tokens.
    2. Unified ccusage session --until X drops Claude and Qwen sessions whose last activity is on day X. filter_session_summaries (rust/crates/ccusage-adapter-all/src/loader.rs) compares the whole lastActivity timestamp as a string, with dashes removed ("20990102T00:00:00.000Z"), against "20990102", so the session fails <= until. It reproduces on 20.0.28: ccusage claude session lists the session, while ccusage session with the same window returns nothing.
    3. unified_reports_return_antigravity_skip_warnings_once_instead_of_printing fails on Windows. It expects antigravity/conversations/... but gets antigravity\conversations\....
  4. github-actions commented on Oct 31, 2026

    @github-actions
    Contributor

    Pullfrog triage: priority:high
    Kind: bug
    Maintenance fit: maintainable
    Confidence: high

    Unified daily sums Claude and OpenCode sources so claude-agent turns stored in both places count twice, with clear token and cost evidence. Alias opus pricing also overstates cost versus the Claude transcript model. This is a material usage and cost error in core reporting.

    Decision: kept open; Pullfrog will attempt a focused implementation PR.

  5. 3 remaining items

  6. igorovh commented on Nov 1, 2026

    @igorovh
    ContributorAuthor

    @ryoppippi thanks for picking this up in #2007! Unified reports now look right for me on v20.0.47. (I tried to reopen this issue, but I don't have permission.)

    There's still double counting for tools that read each source separately. For example, tokenmaxxing syncs claude, opencode, codex, and other sources on their own and adds them up. Since #2007 deliberately keeps focused reports unchanged, Claude Agent turns are still counted under both claude and opencode, and the OpenCode copy is still priced with the opus alias.

    A tokenmaxxing sync --dry-run --since 2026-10-31 for the last three days shows the duplication:

    Source Cost
    opencode $608.25
    claude $535.31

    The claude-agent sessions in the opencode row are the same sessions as in the claude row.

    I'm not sure where this should be fixed. I lean toward ccusage, because it already has the session and day matching from #2007. Each consumer would otherwise have to rebuild it from both data sources. Some options:

    1. Opt-in flag on focused reports, for example ccusage opencode --exclude-claude-agent. The default stays as it is.
    2. A JSON field that marks OpenCode rows already counted by Claude, so consumers can filter them out.
    3. Exclude by default in ccusage opencode, so focused reports partition usage and their sum always equals ccusage daily.
    4. Leave ccusage as is, and I'll open an issue in tokenmaxxing instead.

    Which would you prefer? I'm happy to implement it.

  7. ryoppippi commented on Nov 2, 2026

    @ryoppippi
    Member

    Thanks for the detailed follow-up and the numbers! Let's go with option 3: exclude Claude Agent turns from ccusage opencode by default, so the per-source totals add up to ccusage daily. Claude Code already logs those turns with accurate pricing, so the Claude side should own them. If you'd like, an opt-in flag to include them again would be nice, but it's not required.

    A PR would be very welcome. Please include tests for the exclusion. Thanks for offering to take this on!

  8. igorovh commented on Nov 2, 2026

    @igorovh
    ContributorAuthor

    @ryoppippi option 3 is ready, with the opt-in --include-claude-agent flag and tests:

    main...igorovh:ccusage:fix/opencode-focused-claude-agent

    GitHub still rejects createPullRequest for my account, so I can't open the PR myself. Could you approve me with lgtm, or open it from the branch? The description is ready, and I'll post it as soon as I can.

    Short summary:

    • ccusage opencode skips claude-agent turns that the Claude source counts, using the matching from fix(all): count OpenCode Claude Agent turns once in unified reports #2007. --include-claude-agent keeps them.
    • Focused reports match by session and day in every report kind, session included, so ccusage claude + ccusage opencode equals ccusage daily. On my data for 2026-10-30 to 2026-11-01: $520.34 + $171.26 = $691.60.
    • Claude logs are read only when an OpenCode row links to a Claude session.

    While testing, I found that the unified ccusage session drops Claude sessions that continue past --until. That's separate from this change, so I opened #2016 for it.

  9. igorovh commented on Nov 4, 2026

    @igorovh
    ContributorAuthor

    Hi @ryoppippi, just a gentle ping on this one 🙂

    The option 3 branch is ready, with the --include-claude-agent flag and tests:
    main...igorovh:ccusage:fix/opencode-focused-claude-agent

    GitHub still won't let me open the PR. Whenever you have a moment, could you give me an lgtm, or open it from the branch?

  10. ryoppippi commented on Nov 10, 2026

    @ryoppippi
    Member

    Note

    🤖 Claude Opus 5.5 responding on behalf of @ryoppippi

    Option 3 landed in #2022, which kept your commits from fix/opencode-focused-claude-agent unchanged and added one review-fix commit on top. ccusage opencode now skips Claude Agent turns by default, and --include-claude-agent brings them back. It ships in the next release (v20.0.49). Sorry the PR sat without a reply after your pings. Thanks for the branch and the tests.

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

    bugSomething isn't workingpriority:highHigh user impact or an important regression.triage:maintainableFits the repository's maintainable scope and needs review.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions