This repository was archived by the owner on Jul 1, 2026. It is now read-only.
Add claude-cost-export: local-session cost → Fleet dashboard - #62
Merged
Conversation
Capture the cost of local, interactive Claude Code sessions and ship one event per finished session to the homelab Alloy Loki receiver, so a workstation appears on the "Claude Runner Fleet" Grafana dashboard next to the bullpen agents (job=claude_local, source=local, worker=<hostname>). The bullpen gets total_cost_usd for free from headless `claude -p`; interactive sessions have no result blob, so cost is reconstructed from the transcript (dedupe by uuid + requestId-keeping-max-output, sum tokens per model) × pricing.json. The transcript is the source of truth; capture and shipping are decoupled via a spool so a hard kill (skipping the SessionEnd hook) is caught by the timer's idle backstop, and off-LAN sessions queue and backfill when the LAN returns. Runs are flock-serialized and emit-once guarded; install seeds the guard so only post-install sessions ship. Wired into all four setup-*.sh (non-fatal, after node+jq+repos are present). Prompt-Origin: Chris asked whether the cost metrics the bullpen emits could also be emitted from Claude Code directly, to capture his local sessions in the same dashboard. He preferred a SessionEnd-hook approach and asked about hook reliability and off-LAN buffering; this implements the decoupled transcript-as-truth + spool design that answers both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Origin
Chris asked whether the cost metrics the bullpen emits could also be emitted from Claude Code directly, so his local interactive sessions land on the same "Claude Runner Fleet" Grafana dashboard. We compared native OpenTelemetry vs a hook-based approach; he preferred the hook path and specifically asked how reliably a
SessionEndhook fires and whether sessions could be buffered locally while off-LAN. This implements the design that answers both, and folds it into the bootstrap scripts as a standing workstation standard.What this does
Reconstructs the cost of local, interactive Claude Code sessions from the on-disk transcript and ships one
session_completeevent per finished session to the homelab Alloy Loki receiver (:3100on LXC 105) → Grafana Cloud. A workstation then appears on the Fleet dashboard under a new Local sessions row (job=claude_local,source=local,worker=<hostname>) without touching the fleet's own panels.The bullpen gets
total_cost_usdfor free from headlessclaude -p --output-format json; interactive sessions have no such result blob, so cost is computed from token counts ×pricing.json(cache reads 0.1×, 5m writes 1.25×, 1h writes 2×, Opus fast-mode 2×) — the same API-list-price basis the fleet reports.Design (answers the two questions)
SessionEndis best-effort. It fires on graceful exit but a hard kill (window close, SSH drop, crash) skips it. So the transcript is the source of truth; the hook just drops adone/<id>marker for promptness, and a systemd--usertimer sweeps every 5 min as the backstop (a session idle >COST_IDLE_MINis treated as finished). Nothing is lost to a missed hook.spool.ndjson; the shipper drains it to Loki when reachable; off-LAN POSTs fail and stay queued until the next on-LAN tick.emitted.jsonguard +flockserialization. Install seeds the guard (claims existing sessions without shipping) so only post-install sessions ship — no backfill spike.Token accounting reuses the empirically-derived dedup rules from the
session-reportskill (globaluuiddedup for resumed sessions; per-requestIddedup keeping maxoutput_tokens).Files
claude-cost-export/—cost-export.mjs(sweep+ship),pricing.json,cost-hook.sh(SessionEnd),claude-cost-export.{service,timer}, idempotentinstall.sh,README.md. All foursetup-*.shcall the installer after node+jq+repos are present (non-fatal).Verified
requestIddedup keeps max output (810→800); idempotent re-run spools 0;flockserializes concurrent runs.node --checkpasses.Companion PR
Dashboard row to display this data: PitziLabs/homelab-observability (Local sessions row on
claude-runner-fleet.json).Note
Pricing.json is a static snapshot of platform.claude.com pricing — update when Anthropic changes prices (raw tokens are shipped alongside
cost_usdso totals stay recomputable).🤖 Generated with Claude Code