Skip to content

Commit ec3b22f

Browse files
lroolleclaude
andcommitted
v0.17.0: mitm proxy trust, diagnosable !net, unified +X change flash
Every API fetch died instantly behind a trusted mitm proxy (cctrace, corporate TLS inspection): the statusline inherits HTTPS_PROXY but curl does not honor NODE_EXTRA_CA_CERTS, so TLS verification failed with HTTP 000 and the quota froze behind a persistent !net — 19 consecutive failures over 11 hours observed live, while the CLI kept working. - curl_ca_bundle: splice the extra cert onto a copy of the system CA bundle (curl has no additive flag), --cacert on all four requests - record curl's %{errormsg} in the err file and debug log: cat usage.err now says WHY (SSL/DNS/refused), not just code 000 - header parity with claude-cli 2.1.207 on all OAuth API calls (UA "claude-cli/<ver> (external, cli)", anthropic-beta, Accept, Accept-Encoding); spec doc re-synced, no schema change since 2.1.201 - unified +X/-X change flash (delta_flash): cost $7.90+.37, context [30%]+9 / [3%]-27 after compact, scoped quota fb[69%]+2 — same reverse-video idiom the 5h/7d bump already used - STATUSLINE_NO_FETCH: render from cache/stdin only; exported by the test harness so integration tests stop firing real fetches whose background writes raced teardown (flaky rm -rf) - gitignore .cctrace/ and .env 11 new tests (265 total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 469f08f commit ec3b22f

7 files changed

Lines changed: 384 additions & 32 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ sessions/
66

77
# design mockups (not shipped)
88
assets/palette-retheme-mockup.svg
9+
10+
# local captures and secrets — never version
11+
.cctrace/
12+
.env

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
# Changelog
22

3+
## v0.17.0 — 2026-07-13 — mitm proxy trust, diagnosable !net, unified +X flash
4+
5+
**Fix: every API fetch died behind a trusted mitm proxy (persistent `!net`).**
6+
When the CLI runs under a TLS-inspecting proxy it trusts (cctrace, corporate
7+
inspection), the statusline inherits `HTTPS_PROXY` but curl does not honor
8+
`NODE_EXTRA_CA_CERTS` (Node-only, additive) — every fetch failed instantly
9+
with an SSL verify error (HTTP 000), while the CLI kept working. Observed
10+
live: 19 consecutive failures, quota frozen for 11 hours behind `!net`.
11+
12+
- New `curl_ca_bundle`: splices the extra cert onto a copy of the system CA
13+
bundle (curl has no additive flag) and passes it via `--cacert` on all
14+
four outbound requests (usage, profile, prepaid, OAuth refresh).
15+
- `!net` is now diagnosable: curl's own error text (`%{errormsg}`) is logged
16+
and recorded in the err file — `cat usage.err` says *why* (SSL, DNS,
17+
refused), not just `code 000`.
18+
19+
**Request-header parity with the CLI.** All OAuth API calls now mirror
20+
claude-cli exactly (verified against a v2.1.207 capture): UA
21+
`claude-cli/<version> (external, cli)` with the version of the running CLI,
22+
plus `anthropic-beta: oauth-2025-04-20`, `Accept`, `Accept-Encoding:
23+
identity`. `docs/api/oauth-usage.md` re-synced to v2.1.207 (no schema change
24+
since 2.1.201) and now documents the mitm transport pitfall.
25+
26+
**New: unified +X change flash.** The reverse-video flash the 5h/7d badges
27+
already had is now a generic mechanism (`delta_flash`, one state file per
28+
session) wired into more components — each flashes its change for 60s after
29+
a refresh alters it:
30+
31+
- cost: `$7.90+.37` (cents-precise, dollars shown when the jump is >= $1)
32+
- context: `[█░░░░░30%]+9`, and `[░░░░░░3%]-27` right after /compact —
33+
the drop is the point
34+
- model-scoped quota: `fb[69%]+2`
35+
36+
**Test hygiene: `STATUSLINE_NO_FETCH`.** Integration tests isolate the cache
37+
dirs but `$HOME` (and so real credentials) leaks through — once the SSL fix
38+
landed, a bare cache dir made tests fire *real* API fetches whose background
39+
writes raced teardown (`rm -rf: Directory not empty`, flaky ~1/8). The test
40+
harness now exports `STATUSLINE_NO_FETCH=1`, honored by the script's main
41+
flow: render purely from cache/stdin, never spawn a network fetch. Also
42+
useful standalone for air-gapped/offline setups.
43+
44+
11 new tests (265 total).
45+
346
## v0.16.0 — 2026-07-06 — atomic fetch locks (multi-instance 429 stampede)
447

548
Launching several Claude Code instances together produced 429 bursts. Two

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Every component earns its place:
8080
| Time and cost | Track long sessions. Hours format above 60m (`1h30m`). |
8181
| Model | Abbreviated: `claude-opus-4-8` becomes `opus4.8`, `claude-fable-5` becomes `fabl5`, `claude-sonnet-5` becomes `sonnet5`. The `[1m]` tag marks a 1M-context session, detected from the window the CLI reports (`context_window_size`) — not the name — so it shows even when Claude Code strips the `[1m]` suffix (which it does since 2.1.173 whenever 1M is the default; Sonnet 5 joined Fable 5 on that path in 2.1.197). |
8282
| Effort | Compact lowercase badge: `lo` / `md` / `xh` / `max` / `ultra` / `auto` (`high` is the default and stays hidden). Dim for routine levels; `max` / `ultra` use the pressure color; `fast` shows in fast mode. |
83-
| Context bar | Merged with model. Green / yellow / red by window pressure. On 1M models the bar also carries the premium input-pricing band: yellow past 200k tokens, red past 800k — the % alone looks calm (320k = 32%) while every request bills at the premium rate. A real 0% (e.g. right after `/compact` resets the window) renders a visibly empty bar `[░░░░░░0%]` — the snap to empty *is* the refresh signal; the bar hides only when no context data exists at all. |
83+
| Context bar | Merged with model. Green / yellow / red by window pressure. On 1M models the bar also carries the premium input-pricing band: yellow past 200k tokens, red past 800k — the % alone looks calm (320k = 32%) while every request bills at the premium rate. A real 0% (e.g. right after `/compact` resets the window) renders a visibly empty bar `[░░░░░░0%]` — the snap to empty *is* the refresh signal; the bar hides only when no context data exists at all. Changes flash for ~60s in reverse video: `[█░░░░░30%]+9` while filling, `[░░░░░░3%]-27` right after a compaction. Cost flashes the same way, cents-precise: `$7.90+.37`. |
8484
| User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. |
8585
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — `5h[42%@14:30]` reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge stays relative — `@5d` decays at day granularity, slower than any idle gap.) When a window's utilization climbs between renders, a reverse-video `+N` token appears right after the badge for ~60s: `5h[44%@14:32]+2` means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. **7d is forecast, not leveled**: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows explicit time remaining in the window: `7d[44%@5d]` red means "at your pace, dry days before the reset 5 days from now". Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. **Model-scoped weekly quota**: when the usage API carries a per-model weekly limit (`limits[]`, `kind=weekly_scoped`) for the model your session is running, it renders right after the model+context block — `fabl5[1m][12%] fb[67%]` on a Fable 5 session, `op[33%]` on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining *this* session is signal. Supersedes the legacy `seven_day_opus`/`seven_day_sonnet` fields, which the API now sends as null. |
8686
| Extra usage | Monthly spend, limit, prepaid balance. `--extra auto` shows when quota runs out. |
@@ -107,9 +107,10 @@ red, matching its Claude Code TUI color) = model family; everything else is
107107
| 5 themes, 9 bar styles | -- | Yes |
108108
| Prompt cache break detection | -- | Yes |
109109
| OAuth + macOS Keychain | -- | Yes |
110-
| Quota bump flash (+N) | -- | Yes |
110+
| Change flash on every refresh (`+.37` cost, `+9`/`-27` context, `+N` quota) | -- | Yes |
111111
| Model-scoped weekly quota (`fb`/`op`/`sn`) | -- | Yes |
112-
| 254 bats tests + CI | -- | Yes |
112+
| Works behind trusted mitm proxies (NODE_EXTRA_CA_CERTS) | -- | Yes |
113+
| 265 bats tests + CI | -- | Yes |
113114

114115
## Configuration
115116

@@ -282,7 +283,7 @@ run. Setting only `CLAUDE_CACHE_DIR` keeps the legacy single-dir behavior.
282283
npm exec --yes bats -- t/
283284
```
284285

285-
254 tests across `t/statusline.bats` (242 statusline + integration) and
286+
265 tests across `t/statusline.bats` (253 statusline + integration) and
286287
`t/install.bats` (12 installer). CI runs on push and PR to `main`.
287288

288289
## Project Structure

docs/api/oauth-usage.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ The endpoint this statusline polls for 5h / 7d / per-model quota. This file
44
documents the observed wire contract so parser changes can be checked against
55
ground truth instead of guesswork.
66

7-
- Synced with: **Claude Code v2.1.201** (captured 2026-07-06 via a local
8-
mitm reverse proxy in front of the official CLI)
7+
- Synced with: **Claude Code v2.1.207** (captured 2026-07-13 via a local
8+
mitm reverse proxy in front of the official CLI; first captured against
9+
v2.1.201 on 2026-07-06 — no schema change between the two)
910
- All credentials, org IDs, and request IDs below are redacted or fabricated.
1011
- Fields marked *reserved* were observed only as `null`; names are as sent by
1112
the server.
@@ -16,12 +17,16 @@ ground truth instead of guesswork.
1617
GET https://api.anthropic.com/api/oauth/usage
1718
```
1819

20+
Headers as sent by claude-cli v2.1.207:
21+
1922
| Header | Value | Notes |
2023
|--------|-------|-------|
2124
| `Authorization` | `Bearer <oauth access token>` | The `claudeAiOauth.accessToken` from Claude Code credentials |
22-
| `Content-Type` | `application/json` | |
23-
| `User-Agent` | see below | |
24-
| `anthropic-beta` | `oauth-2025-04-20` | sent by one of the CLI's two internal clients, not required |
25+
| `anthropic-beta` | `oauth-2025-04-20` | |
26+
| `Accept` | `application/json, text/plain, */*` | |
27+
| `Accept-Encoding` | `identity` | |
28+
| `Content-Type` | `application/json` | sent even though the GET has no body |
29+
| `User-Agent` | `claude-cli/<version> (external, cli)` | see below |
2530

2631
No query parameters. No request body.
2732

@@ -37,9 +42,22 @@ user-agent: claude-code/2.1.201
3742

3843
Both received 200 with identical schema. This matters for rate-limit math:
3944
launching N Claude Code instances produces 2N usage requests from the CLIs
40-
alone before any statusline fetch happens. The statusline mimics the second
41-
form (`claude-code/<version>`) and adds at most one request per account per
42-
TTL window (shared cache + atomic lock, see below).
45+
alone before any statusline fetch happens. The statusline mirrors the first
46+
form exactly (`claude-cli/<version> (external, cli)` plus the header set
47+
above, version taken from the running CLI's stdin payload) and adds at most
48+
one request per account per TTL window (shared cache + atomic lock, see
49+
below).
50+
51+
### Transport note: mitm proxies
52+
53+
When the CLI runs behind a TLS-inspecting proxy it trusts (cctrace, corporate
54+
inspection), the proxy's CA typically arrives via `NODE_EXTRA_CA_CERTS`
55+
which only Node honors, *in addition to* the system store. Non-Node tooling
56+
that replays this request (curl, python, go) inherits `HTTPS_PROXY` but not
57+
the trust, and fails instantly with an SSL verification error (curl exit 60,
58+
`http_code` 000). curl has no additive flag: build a combined bundle
59+
(system CAs + the extra cert) and pass it with `--cacert`. This statusline
60+
does that automatically (`curl_ca_bundle` in `statusline.sh`).
4361

4462
## Response — 200
4563

0 commit comments

Comments
 (0)