Skip to content

Fix broken token capture, backfill usage, add cost/token stats per session/project/service - #166

Merged
xmiksay merged 1 commit into
masterfrom
163-token-consumption-stats-per-session__project__serv
Jul 13, 2026
Merged

xmiksay merged 1 commit into
masterfrom
163-token-consumption-stats-per-session__project__serv

Conversation

@xmiksay

@xmiksay xmiksay Bot commented Jul 13, 2026

Copy link
Copy Markdown

Summary

  • Fixed the root bug (blocking discovery in the issue): ClaudeOutput read input_tokens/output_tokens from the top level of the claude CLI's result event, but the real CLI nests them under usage.*. With #[serde(default)] this silently deserialized to 0, so task_sessions.input_tokens/output_tokens have been ~0 in production. total_cost_usd/num_turns (genuinely top-level) were already correct.
  • Verified the result event's usage/total_cost_usd/num_turns are session-cumulative (not per-turn deltas) — consistent with the existing replace_result overwrite semantics already relying on that for cost/turns. Kept the overwrite semantics; backfill takes the last result event per session.
  • Added cache_creation_input_tokens/cache_read_input_tokens (nullable, NULL = "not yet backfilled" marker) to task_sessions via migration.
  • Added an idempotent startup sweep (src/jobs/usage_backfill.rs) that recovers historical rows from the raw result events already persisted verbatim in events.payload.
  • Extended GET /api/tasks/stats with cost/token aggregates (StatsRow/StatsResponse), folded from task_sessions per task.
  • GET /api/tasks/{id} now returns every session (sessions, newest-first) alongside the existing single-session result summary.
  • Frontend: Stats.vue gets cost/token tiles + table columns; task detail gets a new TaskResultPanel.vue (extracted from TaskDetail.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.md updated (module map, database section, HTTP surface) to match.

Test plan

  • make verify (fmt, clippy -D warnings, frontend typecheck) — clean
  • cargo test against both Postgres and SQLite backends — 258 lib tests + 18 integration tests, all green, including new coverage for the parser fix, results.rs overwrite/insert semantics, the backfill sweep (last-event-wins, orphan-zeroing, idempotence), and fold_sessions
  • Frontend vitest — 94 tests green, including formatTokens and TaskResultPanel

Closes #163

…/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.
@xmiksay
xmiksay merged commit 99019cf into master Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Token-consumption stats per session/project/service — fix broken usage capture, backfill from events, extend /api/tasks/stats + UI

1 participant