Repository navigation
Fix broken token capture, backfill usage, add cost/token stats per session/project/service - #166
Merged
xmiksay merged 1 commit intoJul 13, 2026
Conversation
…/token stats What: ClaudeOutput deserialized input/output tokens from the result event's top level, but the claude CLI nests them under usage.* — cost/turns stayed correct but token counts silently landed at 0 in production. Fix the parser, add cache-token columns, backfill historical task_sessions rows from the raw result events already persisted in events.payload, and surface cost/token aggregates in /api/tasks/stats + a per-session breakdown on the task detail. Why: token/cost visibility per session/project/service was requested (#163) but blocked on this capture bug — extending the stats API on top of broken data would have shipped wrong numbers. How: - src/jobs/types.rs: ClaudeOutput.usage now a nested Usage struct (input/output/cache_creation/cache_read tokens), matching the CLI's real result-event shape. - migration + src/entity/task_sessions.rs: two new nullable cache-token columns; NULL is the "not yet backfilled" marker, every write path since sets Some(...) (error paths Some(0)). - src/jobs/usage_backfill.rs: idempotent startup sweep recovering historical rows from events.payload, last result event per session_id wins (matches replace_result's overwrite/cumulative semantics). - src/api/stats.rs: StatsRow/StatsResponse extended with cost/token aggregates, folded from task_sessions per task. - src/jobs/queries.rs, src/api/handlers/*: get_task returns every session (newest first); TaskDetail carries both the existing result summary and the full sessions list. - frontend: types/api.ts additions, new TaskResultPanel.vue (extracted from TaskDetail.vue, now under the file-size cap) with a per-session table, formatTokens util, Stats.vue cost/token tiles and columns.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ClaudeOutputreadinput_tokens/output_tokensfrom the top level of theclaudeCLI'sresultevent, but the real CLI nests them underusage.*. With#[serde(default)]this silently deserialized to 0, sotask_sessions.input_tokens/output_tokenshave been ~0 in production.total_cost_usd/num_turns(genuinely top-level) were already correct.usage/total_cost_usd/num_turnsare session-cumulative (not per-turn deltas) — consistent with the existingreplace_resultoverwrite semantics already relying on that for cost/turns. Kept the overwrite semantics; backfill takes the last result event per session.cache_creation_input_tokens/cache_read_input_tokens(nullable, NULL = "not yet backfilled" marker) totask_sessionsvia migration.src/jobs/usage_backfill.rs) that recovers historical rows from the rawresultevents already persisted verbatim inevents.payload.GET /api/tasks/statswith cost/token aggregates (StatsRow/StatsResponse), folded fromtask_sessionsper task.GET /api/tasks/{id}now returns every session (sessions, newest-first) alongside the existing single-sessionresultsummary.Stats.vuegets cost/token tiles + table columns; task detail gets a newTaskResultPanel.vue(extracted fromTaskDetail.vue, bringing it back under the 400-line cap) showing cache token counts and a per-session breakdown table when a task spans more than one session.docs/architecture.mdupdated (module map, database section, HTTP surface) to match.Test plan
make verify(fmt, clippy-D warnings, frontend typecheck) — cleancargo testagainst both Postgres and SQLite backends — 258 lib tests + 18 integration tests, all green, including new coverage for the parser fix,results.rsoverwrite/insert semantics, the backfill sweep (last-event-wins, orphan-zeroing, idempotence), andfold_sessionsformatTokensandTaskResultPanelCloses #163