Skip to main content

Track Claude Code & Codex Spend

@marginfront/code-cost-clarity is a small command-line tool. One command wires your coding-agent usage telemetry (from Claude Code, Codex, or both) through a local collector and into MarginFront, priced per developer, per model, with accurate prompt-cache token splitting. This is a guardrail, not a hard limit. It tracks spend and can pause work on machines where it’s installed. It can’t stop charges at Anthropic or OpenAI, and it won’t catch a machine that never installed it. For a hard ceiling, set a spend limit in your Anthropic Console or OpenAI account. You’re responsible for your actual provider charges. Want a hard ceiling? See Set a hard spend ceiling for Codex at OpenAI. By default this is internal cost visibility, not billing. Your company watches its own AI coding spend (per developer, per model). It doesn’t charge developers. Out of the box it doesn’t cut anyone off either: turning on spend caps is a separate, opt-in step (see Spend Control below). This tool never reads, needs, or transmits your Anthropic or OpenAI API key. The only credential it uses is your MarginFront key, and only to send usage to MarginFront.
Beta (pilot software). Code Cost Clarity is in active development. Pin a version, expect rough edges, and report issues.
Use npx, not npm install. This is a CLI tool, not a library. Installing it into a project pulls in its dependencies and can surface unrelated audit warnings. Run it with npx @marginfront/code-cost-clarity@latest init (the @latest skips a stale npx cache). The npm i ... box on npmjs.com is npm’s auto-generated default for every package and is not the intended usage here.

What this does (one sentence)

Every time a developer runs Claude Code or Codex, this tool captures the token usage and sends it to MarginFront as a usage event, so you can see who used what, on which model, and how much it cost.

The mental model (read this first)

Think of it like a cash-register receipt system:
  1. Claude Code (and Codex) is the register. As it works, it broadcasts receipts: how many tokens, which model, which developer.
  2. The collector (otelcol-contrib, open source) is the catcher. It runs in the background, catches the receipts, and writes them to a file.
  3. The forwarder (the glue inside this tool) reads those receipts and sends each one to MarginFront.
  4. MarginFront records the event, prices it, and shows it under the developer’s email.
Codex feeds the same catcher, so the collector and forwarder don’t change. Turning Codex on is one config block. See Also capture Codex below. How often does it report? It is timer-based, not per-message. Claude Code flushes usage on the export interval (OTEL_METRIC_EXPORT_INTERVAL) plus a final flush when the session ends. It ships at 5 minutes by default (batched, so you get roughly one event per turn). Lower it (60 seconds or 5 seconds) for a more live drip, or raise it (10 minutes) to batch harder. The collector keeps repeated reports from double-counting no matter the interval.

Quick start

Get your MarginFront key first. Log in at app.marginfront.com, then go to Build -> API keys -> Create key pair and copy the secret key (mf_sk_...). init will ask you to paste it.
Then just code:
That’s it. No source, no second terminal. Your spend shows up in MarginFront under your developer email, automatically, and the background meter restarts itself at every login.
Already running Claude or Codex? Quit and reopen them after init (and start a fresh terminal session). The desktop apps and each terminal session read the telemetry config when they launch, so anything that was already open won’t emit until it’s reopened.
There is no code-cost-clarity command on its own. Installing it does not add a new command to your computer, so typing code-cost-clarity status by itself says “command not found” and nothing is wrong. Every command runs through npx, like the ones on this page.
Check the meter anytime:
Prefer a live terminal view instead of the background meter? After init, run npx @marginfront/code-cost-clarity run to stream your spend in the terminal (Ctrl-C stops it, and shuts its collector down too). You’ll see a line per turn like:
Also use Codex? See Also capture Codex. It’s one config block and the same automatic capture.

What gets captured, and what doesn’t

This meters the coding agents: it reads the claude_code.* and codex.* telemetry that Claude Code and Codex emit to the local collector. init wires telemetry into both places these agents look, so it captures them in the terminal and the desktop apps:
  • Terminal / CLI (claude, codex) and local IDE coding sessions read the config from ~/.claude/settings.json and ~/.codex/config.toml.
  • The Claude Code and Codex desktop apps don’t read that config block, and a Dock-launched app never sees your shell, so init also writes the same vars into the macOS launchd GUI-session environment, which Dock-launched apps inherit. Quit and reopen a desktop app after init so it reads them at launch.
It also labels each MCP tool call with its server and tool name, so a paid MCP tool (a web-search API, an enrichment service) can be priced apart from a free one. Only those two names ever leave your machine; the tool never sends the arguments a tool call was made with. It does not capture:
  • The Claude consumer chat app (the chat window). That’s the general assistant, not Claude Code, so it never emits the coding-agent telemetry this reads.
  • Anything in a cloud sandbox or not connected to your machine: a cloud session, or a remote / devcontainer / SSH setup where the agent can’t reach the local collector at 127.0.0.1:4318. If a surface doesn’t run on your machine and reach the local collector, this can’t see it.

The commands


Spend Control (opt-in caps)

By default this tool only watches spend. Turn on Spend Control and it can stop a Claude Code or Codex tool call when your team’s admin-set cap is reached. What it does. Your admin sets a spend cap in MarginFront (for example, “$200 per developer per week”), in the dashboard’s Money tab or through the Spend Controls API. With Control on, this tool installs a tiny check that Claude Code and Codex both run before every tool call. If the cap is reached, the tool call is stopped with a message that names the cap and tells you to contact your admin for a raise. It runs on the same spend numbers you already meter; nothing new leaves your machine. Turn it on:
You’ll see a consent prompt that spells out the change (Control converts this from a meter that watches you into one that can stop you). Say y to arm it. For Claude Code, it arms on your next claude session (or a /hooks reload). For Codex there’s one extra step, and only you can do it: open codex, run /hooks, and approve the MarginFront cap check. Codex skips hooks nobody has reviewed, so nothing changes there until you do. After arming, cap changes from your admin apply even to sessions already running. Turn it off:
That removes the cap hook and the check script completely, along with the rest of the setup. Honest limits (read these):
  • It fails open. A missing, unreadable, or out-of-date cap file never blocks you. A broken cap should never brick your machine, so it doesn’t.
  • It stops tool calls, not tokens. The prompt that proposed a blocked call is already billed. What the cap kills is unbounded agentic work: the runaway sessions that produce surprise bills.
  • Caps in track mode never block. They only watch and send alert emails. Only a cap in enforce mode that covers all AI tools stops anything on your machine; a cap scoped to a single provider is charted but doesn’t block.
  • Per-developer caps need to know who the developer is. A whole-team cap enforces on every machine. A cap aimed at one developer enforces only on machines where CCC_DEVELOPER_EMAIL names that developer (see Sharing one login across the team). The tool never guesses whose machine this is, because guessing wrong would block the wrong person.
  • It pauses Claude Code and Codex. Gemini and xAI spend still counts toward every cap, but the device cap doesn’t pause those tools.
  • Hosted tools are never paused. A tool that runs on the provider’s servers (like built-in web search) never takes the local tool path, so the check never sees it.
  • An unapproved Codex hook is inert. Until you approve it in /hooks, Codex silently skips it. ccc status tells you which state you’re in, and it says “armed” only when the cap can actually stop something.
  • “Your cap” vs. “the model refused.” If the model itself declines a request (its own safety layer), that isn’t this cap. The block message says so, so you can tell the two apart.
  • Requires python3 on the machine (standard on macOS and Linux dev setups).

Per-developer attribution is automatic

What makes “who spent what” work is the developer’s email, and Claude Code puts it in the telemetry on its own. It’s the developer’s logged-in Claude account email. There is no manual email setup. Each developer just loads the telemetry settings and runs Claude Code normally. Works whether the developer signs in with an org-managed seat or an interactive login. On org-managed Claude seats the email is stamped for free. If a developer’s sign-in doesn’t surface an email, the tool doesn’t drop the usage. It attributes it to a clearly labeled placeholder customer (claude-code-no-identity) and prints how to fix it (sign in with an org-managed seat, or attach a customer mapping). You’ll see the placeholder in preview or run output if it ever kicks in. Codex attributes the same way, with one wrinkle: whether your email shows up depends on how you sign in to Codex (an org-managed seat or an OpenAI API-key sign-in stamps it; some interactive ChatGPT logins may not). That sign-in is between you and OpenAI; this tool never sees your OpenAI key. See Also capture Codex.
Your MarginFront API key is separate from the developer’s identity: it’s the tool’s own credential for posting to MarginFront. run needs it; preview does not.

Sharing one login across the team

Some teams share one Claude or Codex login across the whole team. When that happens, every developer’s telemetry carries the same email, so all the AI cost piles onto one person and per-developer attribution breaks. This tool fixes that without any change to how you sign in, because it runs locally on each laptop, so each laptop can carry its own developer identity. During init it asks one question: which developer this machine’s AI cost belongs to. The question is pre-filled with your global git email (git config --global user.email), which is usually the right person sitting at this laptop. You have three choices:
  • Press Enter to accept the pre-filled email.
  • Type a different email to attribute this machine to someone else.
  • Clear it and leave it blank to auto-detect from whatever email the coding-agent login reports.
Whatever you choose is saved as CCC_DEVELOPER_EMAIL in ~/.marginfront-ccc/.env (mode 600, right next to your key). To change it later, edit that one line and then stop and start (or re-run init). Which email wins, highest first:
  1. CCC_DEVELOPER_EMAIL (this machine’s developer). When set, it wins for every record this machine sends: Claude and Codex, token turns and tool calls.
  2. Otherwise the email the coding-agent login reports.
  3. Otherwise the no-identity placeholder (claude-code-no-identity or codex-no-identity).
Leave it blank and nothing changes: attribution follows the coding-agent login’s own email, then the placeholder.
This is internal cost visibility only. CCC_DEVELOPER_EMAIL re-labels which developer the cost shows up under in your MarginFront. It does not change who pays or any customer’s bill. The --no-prompt install (server or script) skips this question and leaves CCC_DEVELOPER_EMAIL unset.

Cache pricing

Claude Code reports four kinds of tokens (fresh input, output, cache-read, and cache-creation), and Anthropic prices them differently. This tool splits them into their correct typed fields (cacheReadTokens / cacheWriteTokens) so MarginFront can price each at its own cache rate instead of lumping everything into one input number. That token split is accurate by default.
  • Default (recommended). Cache tokens go in the cacheReadTokens and cacheWriteTokens fields; MarginFront prices each at the model’s catalog rate.
  • --fold-cache (emergency round-up only): for a model MarginFront can’t price yet, this rolls the cache tokens into billed input at the fresh-input rate, pushing the number up toward reality so it’s never silently low (a conservative ceiling). Use it only when a model has no cache price; otherwise the default is more accurate.
The dollar figure uses your MarginFront catalog’s rate for each model. If a model isn’t priced yet, its usage lands NEEDS_COST_BACKFILL (a visible gap, never a silent $0), and one click in MarginFront prices it both retroactively and going forward. The raw cache numbers are also kept in metadata, and Claude Code’s own cache-accurate cost is in metadata.claudeCodeCostUsd to reconcile against.

Which cache-write rate you pay (one setup question)

Anthropic bills cache writes at two different rates, and which one you pay depends on how Claude Code signs in on the machine:
  • Claude subscription (Pro / Max / Team): 1-hour cache, billed at 2x input
  • API key / Bedrock / Vertex: 5-minute cache, billed at 1.25x input
init figures this out for you. If the machine already sets FORCE_PROMPT_CACHING_5M or ENABLE_PROMPT_CACHING_1H (in the shell or in ~/.claude/settings.json’s env block), the answer is detected and saved with no question. Otherwise init asks one question and saves the answer as CCC_CACHE_WRITE_TTL in ~/.marginfront-ccc/.env. Every usage record then carries the answer in metadata.cacheWriteTtl, and MarginFront prices that record’s cache writes at the matching rate. If it’s unset (you skipped the question, answered “not sure”, or installed with --no-prompt): MarginFront prices cache writes at the 1-hour ceiling (2x input). It can overstate, but it can never under-count. Edit CCC_CACHE_WRITE_TTL (values 5m or 1h) anytime if the machine’s sign-in changes.
A long-context model id like claude-opus-4-8[1m] is normalized to claude-opus-4.8 to match MarginFront’s pricing table. The raw id is kept in metadata.rawModel.

Also capture Codex

The same tool captures Codex too, and there’s nothing extra to install: Codex reports to the same local collector. You get three setups for free: Claude Code only, Codex only, or both (the same developer’s email shows up across all of it). Turn it on. Add an [otel] block to your ~/.codex/config.toml, pointing at the same collector:
The exporter is a table, with the endpoint nested inside it and ending in /v1/logs. (A flat exporter = "otlp-http" with a bare top-level endpoint silently exports nothing. Verified.) Then run Codex normally. run already watches both sources from the one collector file, so there’s no extra flag. init prints this same block at the end, so you have it handy.

Why Codex’s numbers are counted differently

Anthropic and OpenAI count tokens differently, and getting this wrong silently mis-charges you. For Codex, two of the token counts are already inside the others:
  • The cached tokens are already part of the input count.
  • The reasoning tokens are already part of the output count.
So this tool does not add reasoning on top of output, and does not bill input and cached as if they were separate. The accurate split it sends is: The raw reasoning, tool, and cached counts are still recorded in metadata for your own audit. They’re just never billed twice. (If you ever hit a Codex model MarginFront can’t price yet, --fold-cache works the same conservative way: it bills the whole input at the input rate and drops the cache-read field, so the number reads high, never low.)

Whether your email shows up depends on how you sign in

Per-developer attribution rides on the developer’s email from Codex’s telemetry, the same idea as Claude Code. Whether that email surfaces depends on how you sign in to Codex: an org-managed seat or an OpenAI API-key sign-in stamps it; some interactive ChatGPT logins may not. (That sign-in is between you and OpenAI; this tool never sees your OpenAI key.) If it doesn’t surface, the usage is not dropped. It lands under the codex-no-identity placeholder with a fix hint, exactly like the Claude Code path.

Good to know

  • A gpt-5-codex pricing row must exist first. A full-rate row (including its cache rate) has to be in MarginFront’s pricing catalog. Until it is, a Codex event lands as NEEDS_COST_BACKFILL (a visible gap, never a silent $0). Adding that row is a one-time catalog step.
  • codex-auto-review is skipped. It’s an internal pseudo-model, not a real billable model, so the tool drops it rather than mis-price it.
  • codex and codex exec both export the usage logs this tool reads (verified against a live session). Some Codex versions don’t emit OTel metrics, and codex mcp-server telemetry has had bugs, but neither is the logs stream this tool uses, so neither affects capture here.
  • The [otel] block above is the verified shape (exporter as a table, endpoint nested with /v1/logs). If a future Codex version changes it, keep the exporter pointed at the local collector on /v1/logs.
One thing to confirm: check the exact Codex dollar figure against one real captured session, both that Codex reports per-turn (not running-total) token counts, and that your developer email shows up under your sign-in mode. The mapping above is the verified-safe default; the confirmation is a quick one-session check, not a reason to wait before installing.

Confirm it landed (independent read-back)

Pull the most recent events for one developer straight from the API:

Maintain / shut off

  • Collector version is pinned for safety. The install downloads one known-good collector release and verifies its fingerprint before it ever runs. Moving to a newer collector ships in a package update (so the integrity check always has a fingerprint to verify against). There is no useful per-run version override.
  • Stop temporarily: stop pauses the background meter; start (or re-running init) brings it back. status shows whether it’s running.
  • Remove it: uninstall frees the ~360 MB, stops everything, and reverts the telemetry config it wrote (Claude settings.json, the Codex [otel] block, and the desktop GUI-session env). It keeps your key; uninstall --purge deletes your settings too.

Troubleshooting

  • “No MARGINFRONT_API_KEY found”: paste your MarginFront key into ~/.marginfront-ccc/.env, or export MARGINFRONT_API_KEY=... in the run shell. preview works without one.
  • HTTP 401 / 403: wrong or expired key, or you pasted a publishable key (mf_pk_*) where a secret key (mf_sk_*) is required. The forwarder sends your usage, and publishable keys are rejected on writes. Pull a fresh secret key from your MarginFront dashboard under Build → API keys.
  • HTTP 422 / validation error: body-shape mismatch. Run preview <capture.json> and compare the record.
  • Collector file stays empty: make sure init finished and the background meter is running (status), and that you opened a fresh Claude/Codex session after init (an already-open app or shell won’t emit until reopened). If it’s still empty, re-run init to rewrite the collector config: it defaults to http/protobuf/4318, the transport that works with Claude Code, and gRPC/4317 silently exports nothing.
  • usageCost is null: the normalized model id didn’t match the pricing table. Tokens are still recorded; the cache-accurate cost is in metadata.claudeCodeCostUsd.
  • Numbers ballooning: the collector’s delta conversion isn’t running. Re-run init to rewrite the collector config.
  • Seeing claude-code-no-identity (or codex-no-identity)? Your sign-in didn’t surface an email. Use an org-managed Claude seat, or attach a customer mapping. (Codex uses the codex-no-identity placeholder when a ChatGPT login doesn’t surface an email.)
  • Codex spend not showing up? Confirm the [otel] block is in your user-level ~/.codex/config.toml (not a project config), the endpoint is http://127.0.0.1:4318, and run is going. Some Codex versions/subcommands have telemetry bugs, so check your version.
  • “Collector integrity check FAILED” or “Refusing to run an unverified collector”: the collector that downloaded didn’t match the fingerprint the tool trusts, so it was stopped before it ran. The most common cause is a corporate network that inspects and rewrites downloads (a TLS-inspecting proxy), or a corrupted transfer. Re-run init on a network without that kind of proxy. Don’t try to bypass the check: a download that keeps failing the fingerprint on a clean network is exactly the case the check exists to stop. The “unverified” wording instead means you pointed CCC_OTELCOL_VERSION at a release the tool has no fingerprint for. Unset CCC_OTELCOL_VERSION to use the built-in pinned version.

Security

  • This tool never reads, needs, or transmits your Anthropic or OpenAI API key. The only credential it touches is your MarginFront key, used only to POST usage to MarginFront.
  • Your MarginFront API key never lives in the package. It’s saved only in ~/.marginfront-ccc/.env (mode 600) on your machine. The forwarder reads it from the environment only, never hardcoded, never logged.
  • The published package contains only the built code and its README. Captured telemetry, the collector binary, and your .env are all kept off the machine that runs this tool and out of the package.
  • The collector this tool downloads is checked against a built-in SHA-256 fingerprint (pinned from OpenTelemetry’s signed checksums) before it is ever made runnable. If the downloaded file doesn’t match, it is deleted and the install stops, so a download that was tampered with or rewritten in transit never runs on your machine.

For developers (technical appendix)

Input shape: OTLP/JSON, resourceMetrics[].scopeMetrics[].metrics[]. Two metrics matter: claude_code.token.usage (one datapoint per type in input/output/cacheRead/cacheCreation) and claude_code.cost.usage (USD). Grouping key: (user.email, model, session.id) → one MarginFront record per group. Token mapping: inputinputTokens, outputoutputTokens, cacheReadcacheReadTokens, cacheCreationcacheWriteTokens (Anthropic’s cache_creation_input_tokens). With --fold-cache, cache tokens are added into inputTokens instead and the typed fields are omitted (no double count). Temporality: Claude Code emits cumulative counters. The collector converts them to deltas; the forwarder trusts each line is already an increment. Ingest: POST https://api.marginfront.com/v1/sdk/usage/record, headers Content-Type: application/json and x-api-key: <key> (not Bearer). Body envelope { records: [...] }. The endpoint auto-creates the customer (by customerExternalId) and agent (by agentCode) on first POST, and resolves your org from the MarginFront API key (the body can’t override it).

Codex source (the second input shape)

Input shape: OTLP/JSON logs, resourceLogs[].scopeLogs[].logRecords[] (a different tree than Claude Code’s resourceMetrics). The usage event is named codex.sse_event; it’s detected by that name (on the event.name attribute or the record body) or, failing that, by the presence of any token-count attribute. Value encoding: log attribute values arrive as intValue (a JSON string, per the protobuf int64 rule) or doubleValue (a JSON number) for token counts, and stringValue for identity fields. All three are handled. Token fields: input_token_count, output_token_count, cached_token_count, reasoning_token_count, tool_token_count, plus model, user.email, and a session id (conversation.id / session.id). The accurate nested mapping is in the Codex section above. Cached is subtracted from input, reasoning is never added to output. Granularity: one MarginFront record per codex.sse_event (one per turn). There’s no cumulative-to-delta step on the logs pipeline (that’s a metrics-only processor); each Codex log record is already one turn’s usage. Identity: agentCode: "codex", signalName: "codex-turn", modelProvider: "openai", environment: "development". Records carry metadata.source: "codex". Routing: both sources write one JSON document per line to the same collector file. The forwarder routes each line by which tree it has (resourceMetrics → Claude Code, resourceLogs → Codex), so one watcher handles either or both.