Repository navigation
Description
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
pullfrog commentedon Nov 2, 2026
github-actions commentedon Nov 2, 2026
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.
fix(opencode): count usage from completed v2 compaction rows (ccusage…
chore: credit issue reporters for #2050 and #2051