Files
roboco/docker/scripts/usage-report-hook.sh
T
Renn F cc4ccb7ea3 fix(usage): capture agent token usage from the Claude Code transcript
The token-usage pipeline was fully built — per-session SDK counters,
/usage/status, the orchestrator finalize-fetch that writes token columns and
estimated cost to the spawn-session row, the daily rollup, and the dashboard
— but nothing ever populated the counters. /usage/report had zero callers, so
every session reported zero tokens and the cost dashboard rendered all-zeros.
A redeploy could not fix code that was never written.

Close the loop with the producer that was missing. Claude Code does not pass
token counts to hooks, but it does pass the session transcript path, and each
assistant entry records its API call's usage. Add:

- POST /usage/sync, which parses the transcript and *sets* the cumulative
  totals absolutely (idempotent — re-syncing the same or a grown transcript
  overwrites, never double-counts), with a (size, mtime) short-circuit so an
  unchanged transcript skips the re-parse.
- usage-report-hook.sh, which hands the SDK the transcript path. Registered on
  PostToolUse (keeps mid-run snapshots and reaped-agent sessions accurate) and
  Stop (guarantees a final sync at turn end before finalize reads the totals).

Field mapping verified against a real Claude Code transcript:
message.usage.{input_tokens, output_tokens, cache_read_input_tokens,
cache_creation_input_tokens}. Unit tests cover summation, idempotency, growth,
a missing transcript, and malformed lines.
2026-06-11 07:46:19 +02:00

36 lines
1.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# PostToolUse + Stop: sync token usage from the Claude Code transcript.
#
# Claude Code does not pass token counts to hooks, but it does pass the
# path to the session transcript (.jsonl), which records per-message
# `usage`. We hand that path to the SDK server, which parses the transcript
# and SETS the cumulative totals (absolute, idempotent — safe to call after
# every tool and again at Stop). The orchestrator later reads these via
# /usage/status to finalize the spawn-session row and the usage dashboard.
#
# Fire-and-forget — never block Claude on this. Always exit 0.
set -u
SDK_URL="${ROBOCO_SDK_URL:-http://localhost:9000}"
input=$(cat 2>/dev/null || true)
[[ -z "$input" ]] && exit 0
TRANSCRIPT=$(printf '%s' "$input" | python3 - <<'PY'
import json, sys
try:
d = json.loads(sys.stdin.read())
print(d.get("transcript_path", ""))
except Exception:
print("")
PY
)
[[ -z "$TRANSCRIPT" ]] && exit 0
curl -sf -m 3 -X POST "$SDK_URL/usage/sync" \
-H "Content-Type: application/json" \
-d "{\"transcript_path\":\"$TRANSCRIPT\"}" >/dev/null 2>&1 || true
exit 0