fix(si): Claude 5 era catch-up — pricing/window tables, subagent usage backfill, stdin-first quota, statusline usage row - #7
Merged
Conversation
…age-API outage resilience
Full-stack audit (2026-07-17) found the model-upgrade wave had outrun SI's
tables and the background-agent default had blinded the tracker. Fixes:
Pricing (lib/cost-estimation.js):
- Add claude-fable / claude-mythos families ($10 in / $50 out, 5m cache
write $12.50, cache read $1) — they previously fell through to the
legacy-Opus default.
- Reprice Opus 4.5+ to $5/$25 (rows were costed 3x high); Opus 4.1 and the
dated Opus 4 base id stay on $15/$75 via longer prefix pins.
- Sonnet 5 introductory $2/$10 with a dated flip to $3/$15 on 2026-09-01
(knownPriceForModel(model, at)).
- totalsFromTranscript now prices each turn by its own message.model; the
flat prices arg only covers unknown models. Cost caches bumped v2→v3 so
flat-priced accumulations don't blend in.
- DB migration (user_version 1→2) recomputes agent_invocations.cost_usd
from stored tokens: live opus-4-8 total corrected $1,122.74 → $374.25.
Subagent usage backfill (events.reconcileSubagentUsage, wired in
si-bootstrap):
- 54% of agent rows had NULL model/tokens/cost — Agent/Task calls run in
the background by default now, so PostToolUse fires before the subagent
transcript flushes usage. Reconcile completed transcripts at
SessionStart.
- Claude Code 2.1.x transcripts no longer carry parentToolUseId; pair
agentId ↔ tool_use_id from the parent transcript instead (launch-ack
tool_result text + task-notification markers) via mapAgentToolUseIds.
- Live run backfilled 403 blind rows; the rest have evicted transcripts
and age out of the 45-day window.
Usage-API outage resilience (usage-refresh/usage-api/statusline):
- Honor 429 Retry-After (observed 3111s) instead of re-fetching every
~30s through an outage; 5-minute default backoff for other errors.
- Error caches carry the last good values + goodAt forward; statusline
renders them with a ~ stale marker instead of blanking the b:/w: cells.
Statusline:
- contextCap recognizes fable/mythos as 1M-window models.
- contextPct denominator clamps to min(zones.red, 90% of the model window)
so 200k-window models can actually reach 100%.
Docs: tool-archive path corrected to ~/.claude/state/ (was ${TMPDIR}).
CI: add .github/workflows/test.yml (node 20/22, npm test — 226 tests).
The task cell only ever read ~/.claude/projects/<encoded>/session-context.md.
Projects that hand-maintain a repo-root session-context.md (long-lived
orchestrator sessions like CSM) left the projects-dir copy frozen — its
user-authored content blocks autofill by design — so the statusline showed
a 3-month-old task, permanently marked (stale).
- loadCurrentTask now reads BOTH copies (bounded ancestor walk from cwd
finds the repo-root file) and renders the fresher one.
- session-context parser: section lookup tolerates annotated headings
("## Current Task — #1695 Phase 2 …"), which previously parsed as a
missing section for every consumer (statusline, task-change, pre-compact).
- strip markdown bold markers from the rendered task text.
…stdin
The b:/w: usage cells polled api.anthropic.com/api/oauth/usage — an
endpoint that aggressively 429s pollers (anthropic/claude-code#31637,
closed not-planned). Today it rate-limited all day, Retry-After re-arming
to 3600s, and the cells stayed blank since morning.
Claude Code ≥2.1.90 already passes the quota in the statusline stdin JSON:
rate_limits.five_hour/.seven_day {used_percentage, resets_at}. Prefer that
— fresh every redraw, zero API requests. The oauth poller (with the new
Retry-After backoff + last-good carry) remains as fallback for older
builds only.
Also prefer stdin context_window.context_window_size over the model-name
regex in contextCap — the session's real window beats any name guess.
New bottom line on the verbose/verbose-cache presets showing which models this session's dollars went to, e.g. `fable-5:64% opus-4-8:36%`. Cost share (not token share) via the per-model pricing tables, top 4 models. There is no per-model QUOTA to show — stdin rate_limits only carries the two account-wide windows — so share-of-session-spend is the honest per-model percentage. Both transcript accumulators grow per-model cost buckets (byModel), cache v3 → v4 (bump must stay in lockstep between lib and statusline — they share the cache file).
…t modelQuota field "Used / available for models" — built from what actually exists: - blockUsage/weekUsage render used/available (`b:7/93%`, `w:38/62%`) instead of used-only. - modelSplit adds dollars used per model: `fable-5:$3.55·76%`. - New modelQuota field for per-model QUOTA used/available. Dormant today: verified via a raw 200 that the oauth payload's seven_day_opus/_sonnet/ _cowork buckets exist but are null on plans without per-model limits, and stdin rate_limits only carries the two account windows. The field auto-lights from either source (extra stdin rate_limits keys, or populated seven_day_<model> buckets captured by the usage worker) the day Anthropic ships per-model limits. Endpoint reality check that shaped this: one follow-up probe within a minute of a 200 re-tripped the rate limit with Retry-After 3600 — the endpoint tolerates roughly one request per hour, cementing stdin-first sourcing as the only viable live display.
blockUsage lived on line 2 and weekUsage on line 4; user feedback: all quota/usage belongs together at the bottom. Line 5 is now the usage & models row — `b:7/93% · w:38/62% · [modelQuota] · fable-5:$3.55·76% …` — and lines 2/4 slim down to activity and identity respectively. Same change on verbose-cache.
…ever shows one model The bottom-line model split read only the parent session transcript, and subagents (Explore/reviewers/workflow agents) bill in their own transcript files — so a Fable session with Opus/Haiku agents rendered `fable-5:100%`. Merge per-model agent spend from agent_invocations (events DB) into the split, keyed by sid. New events.agentModelCosts(sid) helper; lazy sqlite load in the statusline (adds ~10ms, cached per redraw process; degrades silently without better-sqlite3). Known lag: background agents record blind until the next SessionStart reconcile fills their usage, so the newest agent runs can be briefly absent from the split.
- New tokenSpeed field, last block of line 2: `tps:238`. Median output speed of the last few streamed turns from lib/token-speed.js — the transcript writes one row per stream chunk sharing the message id, so the per-message timestamp span is pure generation wall-clock and final output_tokens / span is an honest turn speed. Session-average fallback (total output / total_api_duration_ms from stdin) when the tail has no streamed turn. - fmtTokens/fmtTokensTight roll to B past a billion: `t2.4B` instead of `t2422M` — 10-day sessions really do get there.
…5/85%`
The used/available slash form kept being read as a fraction ("15 of
85?"). The remainder now carries its own label so the cell needs no
legend: used% keeps the zone colouring, `free:` and `r:` render dim.
…line 2 Per user feedback: `free:` removed from both quota cells (b:15% / w:39%), and the 5-hour block's reset countdown is now its own blockTimer cell (`blk:2h29m`) at the head of line 2, before session duration — it's a "when do I get budget back" signal that belongs with the live-activity row, not the bottom usage row. Weekly keeps its r: countdown on line 5.
…ckTimer Misread of user feedback: they wanted the blockUsage cell (b:20% with its reset countdown) moved to line 2 before session duration, not a separate timer cell. blockUsage leads line 2 with r: restored; blockTimer removed; line 5 keeps weekly + per-model spend.
…on estimate Requested format `fable-5:sc$78·sp99%·wr[weekly remaining]` per model. True per-model weekly quota doesn't exist (seven_day_<model> buckets are null on plans without per-model limits), so wr is an attribution ESTIMATE, marked `~`: wr(model) = 100 − account_weekly_used% × model's share of this week's tracked spend. Weekly spend tracking: new session_model_costs table (sid, model, cost_usd, t) upserted by the statusline each redraw from the transcript byModel buckets — the only surviving record of parent-session per-model splits once transcripts age out. weeklyModelSpend() unions it with agent_invocations over a 7-day window.
2 tasks
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Full-stack audit (2026-07-17) found the model-upgrade wave had outrun SI's tables, the background-agent default had blinded the tracker, and the usage endpoint had become unpollable. 11 commits, all verified live while dogfooding.
Telemetry correctness
lib/cost-estimation.js): add Claude 5 family (claude-fable/claude-mythosat $10/$50); reprice Opus 4.5+ to $5/$25 (rows were 3x overstated — live DB total corrected $1,122.74 → $374.25 by auser_version1→2 migration recomputing from stored tokens); Sonnet 5 introductory $2/$10 with a dated flip on 2026-09-01; per-turn model pricing in both transcript accumulators (cost caches v2→v4).events.reconcileSubagentUsage, wired intosi-bootstrap): 54% ofagent_invocationsrows had NULL model/tokens/cost — background-default agents fire PostToolUse before their transcript flushes usage. Claude Code 2.1.x also droppedparentToolUseIdfrom subagent transcripts, so pairing now comes from the parent transcript (launch-ackagentId:text + task-notification<task-id>/<tool-use-id>markers viamapAgentToolUseIds). Live run backfilled 403 blind rows.Usage quota (the "missing percentages" outage)
/api/oauth/usageaggressively 429s (anthropic/claude-code#31637, closed not-planned; observed Retry-After up to 3600s, and ~1 req/hour tolerance). Root fix: sourcerate_limitsfrom statusline stdin (CC ≥2.1.90) — fresh every redraw, zero API calls. The oauth poller remains as fallback only, now honoring Retry-After with a 5-minute error floor and carrying last-good values through outages (rendered with a~stale marker).contextCapprefers stdincontext_window.context_window_size; falls back to family regex incl. fable/mythos → 1M.Statusline
b:20% r:2h13m) before session duration; newtokenSpeedcell (tps:238) — median tokens/sec of recent streamed turns from per-message chunk-timestamp spans.w:39% r:2d14h · fable-5:$74·99% opus-4-8:$0.53·1%— weekly quota, dormant per-model quota (modelQuotaauto-lights when Anthropic populatesseven_day_<model>buckets or extra stdinrate_limitskeys), and per-model spend (transcript + subagent spend merged from the events DB viaevents.agentModelCosts).~/.claude/projects/<encoded>/session-context.mdand a repo-root copy (fresher wins) — fixes a 3-month-stale task pin; shared parser tolerates annotated## Current Task — …headings.t2.4B);contextPctclamps to 90% of the real window so 200k-window models can reach 100%.Misc
npm test).~/.claude/state/; README fields/presets updated.Test plan
subagent usage backfilledlog linemodelQuotastays dormant (renders nothing) until per-model buckets are non-null