Token Meter

Token Meter / Guides / Codex token cost

Track your
Codex spend.

Token Meter parses ~/.codex/sessions/**/*.jsonl the same way it parses Claude Code's logs, but Codex's transcript format is different enough that three real bugs shipped and got fixed against it: sessions invisible on a WSL machine, turns billed at the wrong model after a mid-session switch, and tool calls that were silently dropped entirely. This guide covers what's tracked, what still isn't, and the exact commands to check your own data.

Updated 2026-07-15 · Codex tool events, WSL/Windows discovery, and per-turn pricing shipped in 0.1.26; token-meter audit is the newest feature, listed under Unreleased in CHANGELOG.md as of this writing.

Problem

Three separate ways Codex tracking can look broken, each with a real, shipped cause:

Cause

Each of these traces to a specific, documented gap between how Codex writes its JSONL logs and how Claude Code does — not a generic "Codex support is worse" hand-wave:

1. Session directory discovery (WSL/Windows)

codexSessionsDir() resolves to ~/.codex/sessions under Node's homedir(). On native Windows or macOS/Linux that's the only place Codex writes, so it's fine. On WSL, though, a Codex CLI that was only ever run from a plain Windows terminal writes to /mnt/c/Users/<profile>/.codex/sessions — a path a WSL-side scan of ~/.codex/sessions never sees, so every real session there ingested $0, silently. codexSessionsDirs() (src/codex-ingest.ts) now mirrors the same fallback Claude Code project discovery already used: it scans the WSL home path and every /mnt/c/Users/<profile>/.codex/sessions that exists, via scanWindowsUserDirs() in src/platform.ts. That helper deliberately checks which directories actually exist rather than guessing a Windows username — USERPROFILE is often unset under WSL, and the first entry under /mnt/c/Users can be a sandbox/system account (e.g. CodexSandboxOffline), not the real user.

2. Per-turn model-switch pricing

Codex's JSONL log includes a turn_context entry ahead of each turn, carrying the model actually serving that turn in payload.model. Before this was fixed, codex-parser.ts read the model once from session_meta's base_instructions text at the top of the file and billed every token_count event in that session at that one rate — so a session that switched models mid-way (e.g. gpt-5.3-codex-sparkgpt-5.4) had every turn after the switch priced at the stale model's rate. The parser now tracks a currentModel variable, updated from the latest turn_context.payload.model seen, and re-reads it before pricing every token_count event — so pre-switch and post-switch turns land in separate, correctly-priced rows. Logs from older Codex CLI versions with no turn_context entries at all still fall back to the session-start model, because there's nothing else in the file to read.

3. Tool-call pairing

Codex logs a tool invocation as a function_call or custom_tool_call entry carrying a call_id, followed later by a function_call_output / custom_tool_call_output entry carrying the same call_id. Previously these were ignored entirely — a Codex session only ever produced token_events (billing rows), never tool_events — so the tool/MCP breakdown was structurally empty for every Codex session, no matter how many tools it called. codex-parser.ts now holds each open call in a pendingToolCalls map keyed by call_id and, when the matching *_output arrives, emits a ToolEvent — response size and estimated tokens from the output text (stringified via JSON.stringify when Codex emits a structured object/array instead of a plain string, so the size isn't silently undercounted as 0), latency as output-timestamp minus call-timestamp. A call whose session ends before its output ever arrives is simply never flushed — not guessed at, not counted as zero.

Command

Four real surfaces, all reading the local SQLite DB — nothing here calls out to OpenAI.

1. Re-ingest with --force after upgrading

Token Meter's ingest is incremental (mtime + size) — a file it already processed under an older version won't be re-parsed just because the parser logic changed. If you upgraded across the WSL-discovery / model-switch / tool-pairing fixes above, a plain token-meter ingest won't backfill anything for files it already saw; you need one forced pass:

token-meter ingest --force

Expect a much larger files_scanned count for Codex than before if you're on WSL and any sessions were only sitting on the Windows side — that's the previously-invisible data being picked up, not duplication.

2. Scope the audit to Codex

token-meter audit --days 7 --source codex --json

--source accepts all | claude | codex (maps internally to the codex source tag). Drop --json for the terse terminal view, which includes the same per-source Warnings block. --days is clamped to your tier's history cap (7d Free, 30d Pro).

3. Aggregate stats — Codex separates by model name

token-meter stats 7

stats has no --source flag of its own — it always covers every ingested source. In practice Codex is easy to pick out: its rows use gpt-* model ids in the "By model" table, and Codex tool rows always show - in the mcp column of the "MCP & tools" table (see Limitations).

4. From inside your assistant, over MCP

Use token-meter usage_summary with scope "codex" for this week

The usage_summary MCP tool's scope parameter accepts auto (default — filters to your current platform) | all | wsl | linux | win | windows | codex | claude-code. Ask "Use token-meter recent_sessions" for a list that includes ready-to-paste codex resume <id> commands for Codex sessions specifically.

Synthetic output

Fabricated example numbers below — not real user data — to show the shape of each command's output.

token-meter ingest --force (WSL machine, first run after upgrade)

$ token-meter ingest --force
Claude Code: scanned 214, processed 214, +18420 tokens, +6110 tools in 1840ms
Codex:       scanned 97, processed 97, +5310 tokens in 610ms

token-meter audit --days 7 --source codex (terminal)

TOKEN METER AUDIT — LAST 7 DAYS

Sources
codex: partial (312 records analyzed, 100% of window covered — see warnings)

Confirmed usage
Tokens: 214.6k total (in 89.1k, out 61.4k, cache 64.1k)
Estimated cost: $2.1140

Efficiency signals
Findings: 2
Cost associated: $0.6120
Confidence: medium

1. 1 short gpt-5.5 session with minimal output
Source: codex
Project: /home/you/projects/api-gateway
Session: -
Tool: -
Evidence: gpt-5.5 is billed at $30/M output tokens — at or above this audit's high-cost bar of $25.00/M, derived from the current pricing table rather than a hardcoded model name. 8a2f1c9d-... (/home/you/projects/api-gateway): 94s wall-clock, 210 output tokens, $0.6120.
Confidence: low
Suggested action: Review whether every short session required this model.

2. apply_patch calls averaging 4.2s
Source: codex
Project: /home/you/projects/api-gateway
Session: -
Tool: apply_patch
Evidence: Mean latency 4.2s (4200ms) across 6 call(s) with a recorded latency_ms, in the 7d window. p50 (median) 3.8s, p95 6.1s, max 6.4s. failure_rate is not available: tool_events has no error/failure/status column in this schema, so it is reported as null rather than fabricated.
Confidence: medium
Suggested action: Investigate whether apply_patch's output is actually used downstream; if not, drop the call or cache the result to avoid the 4.2s penalty per call.

Warnings
- [codex] cache_inefficiency (D5) does not run for Codex: Codex's usage payload never reports cache_write_tokens, so cache-waste-day detection is structurally unavailable for this source (see src/audit/detectors/cache-inefficiency.ts).

token-meter stats 7 — "By model" excerpt, same session as above, model switch mid-session

=== By model (7d) ===
gpt-5.3-codex-spark            $1.5020  out=42.0k  events=118
gpt-5.4                        $0.6120  out=19.4k  events=14
claude-sonnet-4-6              $12.3300  out=310.0k  events=880

Interpretation

Actions

  1. Codex showing $0 on a WSL machine? First confirm you're on Token Meter ≥ 0.1.26, then run token-meter ingest --force once. If it's still $0, check whether /mnt/c/Users/<your-profile>/.codex/sessions actually exists on the Windows side — if Codex was run from a WSL shell all along, its logs are under the WSL home dir instead, and a $0 result there points at something else (Codex never actually run, or a permissions issue reading ~/.codex/sessions).
  2. Cost looks off after a mid-session model switch? Cross-check the "By model" table (or usage_summary) for that day — you should see one row per model actually used, not one blended row. If you only see the pre-switch model, the session's JSONL predates turn_context support in your Codex CLI version; there's no way to retroactively recover per-turn attribution from a log that never recorded it.
  3. No Codex rows in the tool/MCP breakdown at all? Re-ingest with --force first — tool-event extraction for Codex only exists from 0.1.26 onward, so any Codex session ingested under an older version has token rows but zero tool rows until you re-parse the file.
  4. Comparing a Codex-heavy vs. Claude-Code-heavy week? Use usage_summary scope="codex" and scope="claude-code" separately rather than eyeballing the combined stats output — the scope filter is the one built specifically to avoid mixing platforms/sources, and it also surfaces a "hidden: $X.YY" hint for what the current scope excluded.
  5. Chasing a slow or oversized Codex tool (e.g. apply_patch, shell)? Group by tool_name alone, not (tool_name, mcp_server) — every Codex tool row has mcp_server: null, so grouping by the pair silently collapses every distinct Codex tool that happens to share a name pattern into one bucket labeled the same as any of Codex's own built-ins.

Limitations

← Token Meter home · Source on GitHub · Full audit docs