Skip to content

feat(cost): surface extra-usage and credit consumption in the SPEND display #50

Description

@as-koshiyama

Problem

SPEND (#44/#45) prices seats. When a seat's included usage runs out, both vendors keep working and start billing overflow — Claude "extra usage" (per-seat opt-in flag hasExtraUsageEnabled), ChatGPT/Codex "credits" — and none of that money appears anywhere in surface. The SPEND card silently under-reports exactly when spend accelerates, which is the moment the reader most needs it not to.

What the local data can and cannot say (verified on a real machine, 2026-08-01)

Can say — the trigger:

  • Codex: every token_count record surface already parses carries rate_limits: primary.used_percent / secondary.used_percent (rolling windows, window_minutes, resets_at) and a credits field — null on every record on this machine (weekly window peaked at 39%). Credits going non-null in a transcript is consumption, on lines we already read.
  • Claude Desktop: %APPDATA%\Claude\plan-usage-history.json (version: 2) logs {t, org, u:{fh, sd}} every ~5 minutes per org — the 5-hour (fh) and 7-day (sd) windows as percentages. 3,218 samples on this machine; fh peaked at 75, sd at 22, never 100. A sample at 100 followed by continued usage (ledger has tokens after that timestamp) is the extra-usage trigger.
  • Claude Code: system/api_error transcript records carry a rateLimits field. Observed exactly once here, null, on a connection error — it should populate with the unified rate-limit/overage status on a real 429. Shape-when-populated unverified.

Cannot say — the amount: neither vendor writes overflow dollars to disk. Claude extra-usage charges live in the Team admin console; Codex's credits object shape when active is unknown until someone consumes one.

Design sketch (the house pattern)

Blocked on ground truth, by design

The populated shapes (credits non-null; Claude Code rateLimits on a real 429; whether fh/sd exceed 100 or peg) are unverified, and building parsers against imagined payloads is how silent misattribution happens. Before any PR, capture a real overflow. Probe script (PowerShell, run any time; capture the output the day a cap is hit):

# Codex: last rate_limits per recent session
Get-ChildItem -Recurse "$env:USERPROFILE\.codex\sessions" -Filter *.jsonl | Sort-Object LastWriteTime -Descending | Select-Object -First 3 | ForEach-Object {
  $last = $null; foreach ($l in [System.IO.File]::ReadLines($_.FullName)) { if ($l -match '"token_count"') { $last = $l } }
  if ($last) { ($last | ConvertFrom-Json).payload.rate_limits | ConvertTo-Json -Depth 5 }
}
# Claude: utilization now, and any sample >= 100
$s = (Get-Content "$env:APPDATA\Claude\plan-usage-history.json" -Raw | ConvertFrom-Json).samples
"now: fh=$($s[-1].u.fh) sd=$($s[-1].u.sd)"; $s | Where-Object { $_.u.fh -ge 100 -or $_.u.sd -ge 100 } | Select-Object -Last 3 | ConvertTo-Json -Depth 4
# Claude Code: populated rateLimits on api_error records, if any ever occur
Select-String -Path (Get-ChildItem -Recurse "$env:USERPROFILE\.claude\projects" -Filter *.jsonl).FullName -Pattern '"rateLimits"\s*:\s*\{' -List | Select-Object -First 3 Path

What to do

  • Capture a real overflow payload from at least one vendor (probe above) and record the shape here.
  • Parse the trigger signals in the scan (Codex rate_limits is on lines usage.rs already reads; the Claude history file is a new, cheap source beside an existing config dir).
  • SPEND floors with ▲ extra usage active when triggered; --json carries the state.
  • Decide the [cost.extra_usage] / feat: [cost.budget] and a pace-vs-budget row in the Cost view #25 [cost.budget] interaction so pace and overflow are one story, not two rows.

Related: #25 (budget pacing), #13 (notifications). Verified-quiet case: an account that has never hit a cap shows nothing new — today's dashboards stay exactly as they are.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions