claude-usage-exporter
Export Claude Code token usage -- per agent, model, and token type -- to Prometheus, OTLP, or stdout, parsed straight from the transcripts Claude Code already writes. Works on Max/Pro OAuth subscriptions, where API-key telemetry isn't available.
claude_tokens_total{agent="engineer", model="claude-opus-4-8", token_type="output"} 1840
claude_tokens_total{agent="chief-of-staff", model="claude-opus-4-8", token_type="cache_read"} 39000
Why this exists
If you run Claude Code -- especially a multi-agent setup that shells out to
claude -p -- and you want token spend as time-series metrics in your own
Prometheus/Grafana stack, the existing options don't quite get you there:
- ccusage is excellent for CLI summaries, but it's CLI-only -- no metrics push.
- Claude Code's native OpenTelemetry exports metrics, but short
claude -pruns (often sub-second in an orchestrator) can exit before the metric export interval flushes, so those runs drop their telemetry. It also leans on API-key-style telemetry config rather than a Max/Pro subscription.
The transcripts Claude Code writes to ~/.claude/projects/**/*.jsonl carry the real
per-turn message.usage, are written regardless of how short the run was, and encode
the working directory (hence the agent) in the project-dir name. This tool parses
those into cumulative counters. Same message.usage source as native telemetry, no
flush race, and free per-agent attribution.
Scope, honestly: this is a focused utility for a specific niche -- subscription users who want Claude Code token metrics in their own observability stack. It is not a general "agent telemetry platform," and it stays small on purpose.
Install
# Core (stdout + Prometheus sinks, zero dependencies):
pip install claude-usage-exporter
# With the OTLP sink (pulls in the OpenTelemetry SDK):
pip install "claude-usage-exporter[otlp]"
# Or run without installing, via uv:
uvx claude-usage-exporter --dry-run
Requires Python 3.11+.
Quickstart
See what it would emit from your own transcripts, without writing state or pushing anywhere:
claude-usage-exporter --dry-run
Try it against the bundled sample (note the duplicated message id is de-duplicated):
claude-usage-exporter --dry-run \
--projects-dir examples/sample-transcript \
--labeler-config examples/labeler-openclaw.toml
Sinks
Pick one or more with --sink (repeatable). Default is stdout.
stdout -- debugging / piping (no dependencies)
claude-usage-exporter --sink stdout --format text # exposition-style lines
claude-usage-exporter --sink stdout --format json # structured
Prometheus -- textfile collector (no dependencies)
Writes the node_exporter textfile-collector
format atomically. Point a running node_exporter at the same directory
(--collector.textfile.directory) and it scrapes the file on its next cycle -- no
Prometheus server needed by this tool.
claude-usage-exporter --sink prometheus \
--prometheus-textfile /var/lib/node_exporter/textfile/claude.prom
OTLP -- push to an OTLP/HTTP endpoint (needs the [otlp] extra)
Emits an OTLP ObservableCounter with CUMULATIVE temporality, so rate() /
increase() work across runs. Auth is sent verbatim as the Authorization header
(e.g. Basic <token> for Grafana Cloud), from --otlp-auth or $OTLP_AUTH.
export OTLP_AUTH="Basic <base64-token>"
claude-usage-exporter --sink otlp \
--otlp-endpoint https://otlp-gateway.example.net/otlp
Two OTLP gotchas worth knowing:
- Stable instance id.
service.instance.idis pinned to the hostname by default (override with--service-instance-id/$OTLP_SERVICE_INSTANCE_ID). Left to the SDK it would be a random UUID per process, so a cron-style run would create a new series set every invocation. Keep it stable. _totalsuffix normalization. OTLP-to-Prometheus backends (e.g. Grafana Cloud) move a counter's_totalsuffix to the end of the name.claude_tokens_totalpasses through unchanged; a name likemy_total_metricwould arrive asmy_metric_total. Name your metric so_totalis already terminal.
Per-agent labeling (and "multi-framework" support)
By default there's no agent label -- usage is keyed by model + token_type. To
attribute usage to agents, pass a rules file. Rules are first-match-wins regexes over
the transcript's project-dir name:
claude-usage-exporter --labeler-config examples/labeler-openclaw.toml --dry-run
# examples/labeler-openclaw.toml
label = "agent"
default = "unknown"
match_on = "parent"
[[rules]]
pattern = "-agents-engineer$"
value = "engineer"
The key design point: orchestration frameworks (OpenClaw, Hermes, ...) don't have
their own transcript formats -- they all drive claude -p, and Claude Code writes
the transcripts. The only thing that differs per framework is how a launch directory
maps to an agent. So supporting a new framework is a config file, not code.
How it works
- Incremental -- a per-file byte offset is persisted in a state file; each run reads only newly appended bytes. Scans are sub-second.
- Exact -- messages are de-duplicated by
message.id(Claude Code repeats assistant lines within a turn), and only the top-levelusageis read. - Monotonic -- cumulative totals persist in state and are fully re-emitted every run, producing a proper counter. A state reset reads as a normal counter reset. The state snapshot is written before the push, so a failed push self-heals next run.
State lives at ~/.local/state/claude-usage-exporter/state.json by default. Its size
is dominated by a FIFO-capped (500k) set of seen message ids -- a few MB at most.
Querying tip (OTLP push gotcha)
When pushed via OTLP, samples can be sparse -- sparser than Prometheus's default 5-minute staleness window. A plain instant query for the metric can look empty even when everything is healthy. Probe with a long-window function instead:
# Does data exist / how fresh is it?
count(last_over_time(claude_tokens_total[60d]))
# Activity over the last 7 days, per agent:
sum by (agent) (increase(claude_tokens_total[7d]))
Running on a schedule
Generic systemd user units are in examples/systemd/ (a oneshot
service + a 10-minute timer). Put secrets in an EnvironmentFile rather than
source-ing them, so a value containing a space (like Basic <token>) survives intact.
Architecture
Three seams, each independently swappable:
Source Labeler Sink
(Claude Code jsonl reader) --> (path -> {agent, ...}) --------> (stdout / prometheus / otlp)
core.py labelers.py sinks.py
- Source (
core.py) -- the only code that knows the Claude Code transcript format. One implementation; deliberately not abstracted over other agent CLIs (Codex, Cursor, ...) until there's real demand -- that's the documented seam for a future contributor. - Labeler (
labelers.py) -- turns a transcript into labels. Config-driven, so new frameworks need no code. - Sink (
sinks.py) -- turns cumulative totals into a destination write. Adding one (Datadog, StatsD, remote-write) is a ~20-line class implementingemit(metric_name, samples).
Development
pip install -e ".[otlp,dev]"
pytest
The stdout and Prometheus sinks are fully verified offline; the Prometheus output is
additionally linted with promtool check metrics when promtool is on PATH. The OTLP
mapping is unit-tested via the OpenTelemetry SDK without needing a live endpoint.
Metric reference
Single metric: claude_tokens_total (configurable via --metric).
| Label | Values |
|---|---|
token_type |
input, output, cache_read, cache_creation |
model |
the model id from the transcript |
agent |
whatever your labeler assigns (absent if none) |
Related
- ccusage -- excellent CLI for Claude Code usage summaries. Complementary: ccusage is for interactive CLI reporting; this tool is for pushing continuous metrics to Prometheus/OTLP.
License
MIT -- see LICENSE.
Metadata
Release files for claude-usage-exporter 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| claude_usage_exporter-0.1.2.tar.gz | 18.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| claude_usage_exporter-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.7 kB
Release files / claude_usage_exporter-0.1.2.tar.gz
| Download URL | claude_usage_exporter-0.1.2.tar.gz |
|---|---|
| Size | 18.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ebd2c8ea12fa2c57aaed69dcdea683cfc3d6a48571dc3b2a027d2919e80b0a78
|
|
BLAKE2b-256 checksum How to use checksums |
5aff56d3f53c67ea95e8cc7b0984abc63d5d4ae22d44c9b03bbfcc2c99f46b7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency logRelease files / claude_usage_exporter-0.1.2-py3-none-any.whl
| Download URL | claude_usage_exporter-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ab8ef03d29f9280dbb798b3ead759a2b2b94f47f7401aa367e7fcf6f09737cc6
|
|
BLAKE2b-256 checksum How to use checksums |
9941724a089f9abceaf7aaa1a790818ad1843480388b2445879dd937166e06f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.
Transparency log