Repository navigation
[Bug]: local_runtime_token_usage keeps model NULL for historical rows, so downstream tooling cannot price them #540
Description
Product or interface
Desktop app (the local-runtime usage ledger that third-party tooling reads).
Version
Desktop app 3.1.6 (build from 2026-10-23). The ledger lives in
~/.minimax/v2/sqlite/runtime-state.sqlite, table local_runtime_token_usage.
Platform
macOS (Apple Silicon).
OS version and architecture
macOS 27.0.1, arm64.
Issue area
Runtime / local-runtime — token usage ledger (local_runtime_token_usage).
Steps to reproduce
-
Use MCode on a build that predates the model being written, then upgrade to a
build that writes it (the fix is visible in the current build: rows written
after 2026-10-24 01:06 local carrymodel = 'minimax/MiniMax-M3.1-Flash'). -
Inspect the ledger:
sqlite3 ~/.minimax/v2/sqlite/runtime-state.sqlite \ "SELECT model, COUNT(*), SUM(input_tokens+output_tokens) FROM local_runtime_token_usage GROUP BY model;"
On my machine:
| | 2928 | 30859470 | minimax/MiniMax-M3.1-Flash | 105 | 5845270Every row written before the fix has
model IS NULL; only rows written after
it carry a model.
Expected and actual behavior
Expected: the ledger records the model for every usage row, so downstream
tooling can attribute usage to a model.
Actual: historical rows keep model IS NULL forever. Third-party usage
trackers that read this ledger (for example vibe-usage, which reads
local_runtime_token_usage and prices each bucket by its model string) show
those rows as model unknown and therefore cost $0.00, even though the model
is unambiguous.
Why it matters
The model is recoverable from data the runtime already keeps: the per-session
history files under ~/.minimax/v2/sessions/<history_relative_dir>/messages.jsonl
carry message.model and turn_id for every assistant turn. Joining those to
the ledger rows by session_id + turn_id recovers the model for 100% of the
rows I could check (2 621 / 2 621 exact matches by turn_id, the remainder
recovered from the session's dominant model).
Proposal
- One-off backfill. A migration that fills
modelfor rows where it is
NULL, reading the model from the session history files (the runtime owns
those files, so this does not widen any privacy boundary). A reference
implementation is available — happy to open a PR if this is welcome, but I
understand the repository currently only accepts contributions from
collaborators, so an issue is the right route. - Or: surface it. When the ledger has rows with a NULL model, report the
count somewhere observable (astatus/diagnostics command or a log line),
so tooling and users can tell "no model recorded" apart from "zero usage". - Or: both.
Reference implementation
The recovery I used (Python 3, read-only on the ledger, writes only the model
column of rows where it is NULL):
# 1. (session_id, turn_id) -> model, from the session history files
for session_id, rel in sqlite3.connect(DB).execute(
"SELECT session_id, history_relative_dir FROM local_runtime_sessions "
"WHERE history_relative_dir IS NOT NULL"):
for line in open(f"~/.minimax/v2/sessions/{rel}/messages.jsonl"):
obj = json.loads(line)
msg = obj.get("message") or {}
if msg.get("model"):
turn2model[(session_id, obj.get("turn_id"))] = msg["model"]
# 2. fill the NULL rows
UPDATE local_runtime_token_usage SET model = ? WHERE id = ? # turn_id match
# (fallback: the session's dominant model, for rows whose turn is gone)Related
- Third-party tracker side:
vibe-cafe/vibe-usage#127adds mcode session
records; the missingmodelon historical rows is the remaining reason those
rows still price at $0.00.
Activity