Summary
graphifyy==0.8.36 records per-run token usage in graphify-out/cost.json with only one side of the ledger per run: a run books either input_tokens or output_tokens, never both (the other side is 0). The total_input_tokens / total_output_tokens sums over these one-sided records — and the - Token cost: N input · M output line report.py renders into GRAPH_REPORT.md — therefore understate real usage and mislead any human or CI gate that trusts them.
Environment
- graphifyy version: 0.8.36 (exact pin in CI; latest at time of filing: 0.9.63 — unverified whether still affected)
- OS: ubuntu-latest (GitHub Actions runner), Python 3.11
- Command:
graphify update <repo> --force invoked from CI after each merge to main
Minimal reproduction
Two consecutive graphify update runs on the same repository (CodeBus), from the committed graphify-out/cost.json:
{
"runs": [
{
"date": "2026-06-09T20:34:51.846794+00:00",
"input_tokens": 0,
"output_tokens": 62833,
"files": 44
},
{
"date": "2026-06-12T06:46:50.591041+00:00",
"input_tokens": 64786,
"output_tokens": 0,
"files": 168
}
],
"total_input_tokens": 64786,
"total_output_tokens": 62833
}
Steps:
pip install graphifyy==0.8.36
- Run
graphify update <repo> --force against a corpus that requires LLM extraction (run A).
- Run
graphify update <repo> --force again after repo changes (run B).
- Inspect
graphify-out/cost.json and the - Token cost: line in graphify-out/GRAPH_REPORT.md.
Expected
Each run records both sides of its own usage (input_tokens > 0 and output_tokens > 0 for any run that performed LLM extraction), and totals are the sum of complete per-run records.
Actual
- Run A recorded
input_tokens: 0, output_tokens: 62833 — extraction of 44 files consumed input tokens; they were not captured.
- Run B recorded
input_tokens: 64786, output_tokens: 0 — extraction of 168 files produced output tokens; they were not captured.
GRAPH_REPORT.md (rendered from the latest run) claims - Token cost: 64,786 input · 0 output, i.e. "this build produced zero output tokens", which is impossible for a run that extracted 168 files.
- The totals (
64786 / 62833) are sums over one-sided records: each total comes entirely from a different run.
The pattern suggests the usage-capture path differs per backend/mode: one code path populates only output_tokens (e.g. an agent/CLI-backend result whose input_tokens placeholder is never overwritten), the other only input_tokens, and the merged/persisted record keeps the zeros instead of failing loudly or marking the side as unknown.
Impact
- Any cost dashboard, budget gate, or human reading
cost.json / GRAPH_REPORT.md silently under-accounts real token spend (one side is always 0 per run).
- Downstream CI (ours: CodeBus
graphify-on-merge.yml) publishes these numbers into an internal report; we now have to mark the accounting UNTRUSTED until fixed.
Related
Suggested fix direction
- In every backend's usage capture, treat a missing side as unknown, not
0, and refuse to persist a run record with an unknown side without flagging it (e.g. "input_tokens": null + a partial: true marker).
- Make
report.py's - Token cost: line render partial/unknown records explicitly instead of printing a confident 0.
- Add a regression test asserting a two-run sequence where both runs did LLM work yields
input_tokens > 0 && output_tokens > 0 in each run record.
Reported from CodeBus issue #271 (Bragi-GmbH/CodeBus). Full in-repo write-up: docs/graph/upstream-graphify-cost-json-bug.md.
Summary
graphifyy==0.8.36records per-run token usage ingraphify-out/cost.jsonwith only one side of the ledger per run: a run books eitherinput_tokensoroutput_tokens, never both (the other side is0). Thetotal_input_tokens/total_output_tokenssums over these one-sided records — and the- Token cost: N input · M outputlinereport.pyrenders intoGRAPH_REPORT.md— therefore understate real usage and mislead any human or CI gate that trusts them.Environment
graphify update <repo> --forceinvoked from CI after each merge to mainMinimal reproduction
Two consecutive
graphify updateruns on the same repository (CodeBus), from the committedgraphify-out/cost.json:{ "runs": [ { "date": "2026-06-09T20:34:51.846794+00:00", "input_tokens": 0, "output_tokens": 62833, "files": 44 }, { "date": "2026-06-12T06:46:50.591041+00:00", "input_tokens": 64786, "output_tokens": 0, "files": 168 } ], "total_input_tokens": 64786, "total_output_tokens": 62833 }Steps:
pip install graphifyy==0.8.36graphify update <repo> --forceagainst a corpus that requires LLM extraction (run A).graphify update <repo> --forceagain after repo changes (run B).graphify-out/cost.jsonand the- Token cost:line ingraphify-out/GRAPH_REPORT.md.Expected
Each run records both sides of its own usage (
input_tokens > 0andoutput_tokens > 0for any run that performed LLM extraction), and totals are the sum of complete per-run records.Actual
input_tokens: 0, output_tokens: 62833— extraction of 44 files consumed input tokens; they were not captured.input_tokens: 64786, output_tokens: 0— extraction of 168 files produced output tokens; they were not captured.GRAPH_REPORT.md(rendered from the latest run) claims- Token cost: 64,786 input · 0 output, i.e. "this build produced zero output tokens", which is impossible for a run that extracted 168 files.64786 / 62833) are sums over one-sided records: each total comes entirely from a different run.The pattern suggests the usage-capture path differs per backend/mode: one code path populates only
output_tokens(e.g. an agent/CLI-backend result whoseinput_tokensplaceholder is never overwritten), the other onlyinput_tokens, and the merged/persisted record keeps the zeros instead of failing loudly or marking the side as unknown.Impact
cost.json/GRAPH_REPORT.mdsilently under-accounts real token spend (one side is always 0 per run).graphify-on-merge.yml) publishes these numbers into an internal report; we now have to mark the accounting UNTRUSTED until fixed.Related
null/unknown rather than0when a side wasn't observed).Suggested fix direction
0, and refuse to persist a run record with an unknown side without flagging it (e.g."input_tokens": null+ apartial: truemarker).report.py's- Token cost:line render partial/unknown records explicitly instead of printing a confident0.input_tokens > 0 && output_tokens > 0in each run record.Reported from CodeBus issue #271 (Bragi-GmbH/CodeBus). Full in-repo write-up:
docs/graph/upstream-graphify-cost-json-bug.md.