anomalyco / anomalyco/opencode
opencode2 stats under-reports spend when a forked session's parent is deleted
@jlongster is already working on this.
Since Sep 10, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Issue Report: OpenCode under-reports spend when a forked session's parent is deleted
Summary
opencode2 stats (and the aggregated cost / token columns in the session_v2 table) excludes the cost of messages that a forked session inherited from its parent even when the parent session no longer exists in the database. If the parent is deleted, the inherited messages are the only remaining record of that spend, so real, provider-billed usage silently disappears from all reports.
Environment
- OpenCode v2 (
opencode2 v0.0.0-beta-19425), data in~/.local/share/opencode/opencode.db - Sessions forked from other sessions (
session_v2.fork_session_id) - Verified on Linux, September 2026
How fork accounting works today
When a session is forked, OpenCode copies the full parent message history into session_message under the fork's session_id, with new message IDs and the original cost values embedded in each message's data JSON. The aggregated session_v2.cost column of the fork only reflects messages created after the fork's time_created.
The under-reporting bug triggers when the parent session row is deleted while the fork survives:
- The inherited messages in the fork are the only record of the cost paid for the parent conversation.
- Nothing counts them: the parent is gone, and the fork's aggregate excludes them by design (they predate the fork's creation).
Reproduction (observed data)
Database state on the day of the fork:
- A session was forked at 13:44 from a parent session.
- The parent session row no longer exists (neither in
session_v2nor in the legacysessiontable) and has no messages. - The fork contains 380 assistant messages; the 130 messages created before 13:44 (inherited history) carry $2.8987 of real cost.
- Content-hashing all 552 assistant messages of that day shows zero duplicated content: every inherited message is unique in the database.
Resulting numbers for that day:
| Source | Value |
|---|---|
Sum of cost across all assistant messages of the day |
$4.8018 |
Sum excluding fork-inherited history (current opencode2 stats behavior) |
$1.9031 |
| Provider-side web console (internal reporting tool) | ~$5 |
The provider-side report (~$5) matches the full per-message sum: the $2.90 of inherited messages was real, billed usage.
Root cause
Observed behavior: a fork's aggregate (session_v2.cost) only reflects post-fork messages, and deleting the parent session removes the only other record of the inherited spend. The internal mechanism behind the aggregate (an exclusion query at read time vs. incremental aggregation at write time) has not been inspected; either way, the result is the same — inherited spend is not counted anywhere once the parent is gone.
Possible fixes
The right fix depends on where the aggregation actually happens internally (read-time query vs. write-time incremental update), which is up to the maintainers to determine. Candidate approaches by layer:
-
If
session_v2.costis an incremental aggregate (most likely): when a session is deleted, preserve its aggregated cost/token totals in a rollup table (or reattribute inherited spend to surviving forks at fork time). -
If stats are computed by querying
session_message: include a fork's inherited history when the parent no longer exists. Reference SQL validated against a real database:AND NOT ( s.fork_session_id IS NOT NULL AND EXISTS (SELECT 1 FROM session_v2 p WHERE p.id = s.fork_session_id) AND m.time_created < s.time_created )With
sbeing the fork'ssession_v2row joined on
s.id = m.session_id.IS NOT/IS NOT NULLare used deliberately for
null-safe comparisons.
Note: the SQL above is a reference implementation validated only in a local reporting tool (where the calculation method is fully known); it is not a patch for the core, and patching the core at a different layer may be simpler.
Related observation: deleted sessions lose their messages
In the observed database the deleted parent also lost its session_message rows (foreign-key cascade), which is what makes the inherited copy in the fork the only record. If deleting a session is expected to keep historical cost reporting accurate, OpenCode could consider:
- Keeping aggregated
cost/token totals of deleted sessions in a separate rollup table, or - Making
session_v2.costof a fork include its inherited history (with the parent-exists caveat above), or - Documenting that deleting a session discards its cost accounting and that forks become the only record of inherited spend.
Impact
- Any user who forks sessions and later deletes parents under-reports real spend in
opencode2 statsand in anything reading session_v2.cost`. - In a sampled 10-day window, the gap was $20.34 (reported) vs $23.37 (real): ~13% of the period's spend was invisible.
Verification of the reference rule
Against the same real database (same 10-day window), applying the reference SQL from the previous section:
- One day with two forks of a live parent: $8.03 (the dedup correctly removed $11.99 of inherited history; content-hash confirms exact duplicates)
- One day with an orphan fork: $4.80 (inherited history counted as real spend; matches provider console)
- Total: $23.37, consistent with provider-side reporting.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.