Skip to content

OpenCode v2 reports omit usage from completed compaction rows #2013

Closed

Description

@arthurnunesc

What happened?

ccusage opencode skips token-bearing session_message rows with
type='compaction'. A synthetic session with 12 assistant tokens and 162
compaction tokens reports only 12 tokens and $0.01 instead of 174 tokens and
$0.03. Daily, weekly, monthly, and mixed-session reports are affected.

OpenCode defines these tokens as usage of the compaction request itself, not
the size of the resulting context
.
Its compaction requests are separate from assistant steps. Missing these rows
understates recorded usage and cost.

The assistant-only filter
is still present on current main (c4177eeb). I found the original v2 work in
#1644/#1668, but no report specifically covering compaction usage.

Steps to reproduce

Save this as repro.py and run python3 repro.py /absolute/path/to/ccusage
with the 20.0.47 native executable. It uses only synthetic data, a disposable
home/config/cache, offline mode, and an explicit OpenCode source directory.

import atexit, json, pathlib, shutil, signal, sqlite3, subprocess, sys, tempfile

binary = str(pathlib.Path(sys.argv[1]).resolve(strict=True))
root = None
def cleanup():
    if root is not None:
        shutil.rmtree(root)
atexit.register(cleanup)
def interrupt(*_):
    raise SystemExit(130)
signal.signal(signal.SIGINT, interrupt)
signal.signal(signal.SIGTERM, interrupt)
root = pathlib.Path(tempfile.mkdtemp(prefix="ccusage-compaction-"))
for name in ("home", "config", "cache", "data", "tmp", "opencode"):
    (root / name).mkdir()
(root / "config.json").write_text("{}")
db = sqlite3.connect(root / "opencode/opencode.db")
db.execute("CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, seq INTEGER, time_created INTEGER, data TEXT)")
for seq, kind, tokens, cost in [
    (1, "assistant", dict(input=10, output=2, reasoning=0, cache=dict(read=0, write=0)), .01),
    (2, "compaction", dict(input=100, output=20, reasoning=7, cache=dict(read=30, write=5)), .02),
]:
    data = dict(status="completed", model=dict(id="gpt-5", providerID="openai"),
                time=dict(created=1767312000000), tokens=tokens, cost=cost)
    if kind == "compaction":
        data.update(reason="auto", summary="Synthetic summary")
    else:
        data["content"] = []
    db.execute("INSERT INTO session_message VALUES (?,?,?,?,?,?)",
               (f"synthetic-{seq}", "synthetic-session", kind, seq, 1767312000000, json.dumps(data)))
db.commit()
db.close()
env = dict(PATH="/usr/bin:/bin", HOME=str(root / "home"),
           XDG_CONFIG_HOME=str(root / "config"), XDG_CACHE_HOME=str(root / "cache"),
           XDG_DATA_HOME=str(root / "data"), TMPDIR=str(root / "tmp"),
           OPENCODE_DATA_DIR=str(root / "opencode"), LOG_LEVEL="0")
subprocess.run([binary, "opencode", "daily", "--json", "--offline", "--mode", "display",
                "--timezone", "UTC", "--config", str(root / "config.json")],
               env=env, cwd=root, check=True, timeout=15)

Actual totals: 12 tokens, $0.01. Expected: 174 tokens, $0.03.
Changing only the synthetic compaction row's type to assistant makes the
same payload count, which isolates the problem to row selection.

Expected behavior

Count completed, model-attributed compaction request usage once, alongside
ordinary assistant usage, using the recorded timestamp, model, and cost.
Keep message-ID deduplication and fork-copy exclusion, and do not add cumulative
session counters on top of message usage.

In additional synthetic checks, duplicate source roots still report 12 instead
of 174 tokens. A fork that copies both parent rows and generates 4 new assistant
tokens reports 16 instead of 178. These expected totals count inherited requests
only once. An unbounded compaction-only session can already appear through the
aggregate fallback, but that does not repair daily reports or mixed sessions.

I'm happy to implement a focused Rust-adapter fix and regression tests after
you confirm the scope. Failed compactions without a model and background usage
without a message row need separate attribution decisions; this report does not
claim to recover every compaction-related charge.

Version

ccusage 20.0.47, macOS arm64, Python 3.9.6. OpenCode V2 record semantics
checked against anomalyco/opencode@ff90ed916 (@opencode/core source version
2.2.5); OpenCode itself was not run. The relevant ccusage loader/parser files
are identical in v20.0.47 and current main c4177eeb.

Activity

  1. pullfrog commented on Nov 2, 2026

    @pullfrog
    Contributor
  2. added
    bugSomething isn't working
    priority:mediumA normal bug or meaningful improvement.
    triage:maintainableFits the repository's maintainable scope and needs review.
    on Nov 2, 2026
  3. added
    priority:highHigh user impact or an important regression.
    and removed
    priority:mediumA normal bug or meaningful improvement.
    on Nov 2, 2026
  4. github-actions commented on Nov 2, 2026

    @github-actions
    Contributor

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

    OpenCode v2 loader skips non-assistant session_message rows, so completed token-bearing compaction requests are omitted from daily and other reports. Repro shows 12 vs 174 tokens and $0.01 vs $0.03, isolated to row selection. Counting those rows once with existing dedup and fork exclusion directly protects usage and cost accuracy with bounded cost.

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

  5. added a commit that references this issue on Nov 10, 2026
    b85b28f
  6. added a commit that references this issue on Nov 10, 2026
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