#20428: feat: capture Anthropic rate-limit response headers to disk
agents
size: L
Cluster:
AI Provider Enhancements
## Problem
For users on **Claude Max** (subscription via OAuth / Claude Code), there is **no programmatic API** to query remaining usage quota. The Anthropic `/usage` page shows session limits, weekly caps, and extra-usage budget, but this data is not available via any API endpoint.
The **only machine-readable signal** about rate limits comes from HTTP response headers that Anthropic returns on every API call:
```
anthropic-ratelimit-unified-limit
anthropic-ratelimit-unified-remaining
anthropic-ratelimit-unified-reset
anthropic-ratelimit-unified-tokens-limit
anthropic-ratelimit-unified-tokens-remaining
anthropic-ratelimit-unified-tokens-reset
```
These headers are currently **discarded** — the Anthropic Node SDK does not surface response headers to callers, and pi-ai (the streaming layer) does not expose them either.
## Solution
Add a new module (`anthropic-ratelimit.ts`) that captures these headers using a scoped `fetch` wrapper:
1. **Before** each Anthropic streaming API call, a lightweight fetch hook is installed
2. The hook intercepts the HTTP response, extracts any `anthropic-ratelimit-*` / `retry-after` / `x-ratelimit-*` headers
3. **After** the stream ends (done/error/break), the hook is uninstalled
4. Captured headers are atomically written to `<stateDir>/anthropic-ratelimit.json`
### Output format
```json
{
"ts": "2025-02-18T14:30:00.000Z",
"headers": {
"anthropic-ratelimit-unified-limit": "1000",
"anthropic-ratelimit-unified-remaining": "750",
"anthropic-ratelimit-unified-reset": "2025-02-18T15:00:00Z",
"anthropic-ratelimit-unified-tokens-limit": "100000",
"anthropic-ratelimit-unified-tokens-remaining": "85000",
"anthropic-ratelimit-unified-tokens-reset": "2025-02-18T14:35:00Z"
},
"sessionKey": "main:telegram:...",
"modelId": "claude-opus-4-6"
}
```
### Design choices
- **Always active** for Anthropic models — no env-var opt-in. The overhead is minimal (one `JSON.stringify` + one atomic file write per API call)
- **Scoped fetch wrapper** — only installed for the duration of a single streaming call, not globally
- **Separate from payload logging** — `OPENCLAW_ANTHROPIC_PAYLOAD_LOG` controls detailed request/response logging; rate-limit capture is independent
- **Atomic writes** — uses write-to-temp + rename to prevent corrupt reads
- **Read helper** — `readRatelimitSnapshot()` exported for internal use
## Use cases
- **Budget dashboards** — menu-bar apps or CLI tools can read `anthropic-ratelimit.json` to show remaining quota
- **Monitoring** — alert when approaching rate limits before hitting them
- **Usage tracking** — external tools can correlate rate-limit data with session JSONL token counts
- **Auto-throttling** — future OpenClaw features could use this to pace requests
## Changes
| File | Change |
|------|--------|
| `src/agents/anthropic-ratelimit.ts` | New module: fetch hook, snapshot writer, reader |
| `src/agents/anthropic-ratelimit.test.ts` | 5 unit tests |
| `src/agents/anthropic-payload-log.ts` | Export `createRatelimitStreamWrapper` + import |
| `src/agents/pi-embedded-runner/run/attempt.ts` | Wire up ratelimit wrapper for all Anthropic calls |
## Testing
- 5 unit tests covering: header capture, non-Anthropic URL filtering, fetch restoration, no-header graceful handling, idempotent install/uninstall
- Full TypeScript compilation passes with zero errors
- Existing test suite unaffected
## References
- [claude-code#19385](https://github.com/anthropics/claude-code/issues/19385) — no programmatic access to ratelimit data
- [Anthropic rate limits docs](https://docs.anthropic.com/en/api/rate-limits) — header format specification
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds a new `anthropic-ratelimit.ts` module that captures Anthropic rate-limit response headers (`anthropic-ratelimit-*`) by temporarily wrapping `globalThis.fetch` during streaming API calls, and writes snapshots to `<stateDir>/anthropic-ratelimit.json` for external consumption by dashboards and CLI tools. The wrapper is wired into `attempt.ts` unconditionally for all Anthropic model calls.
- **`isAnthropicUrl` false positives**: The URL check `href.includes("anthropic")` matches third-party Anthropic-compatible providers (MiniMax, Xiaomi, Cloudflare AI Gateway) configured in `models-config.providers.ts` that use `api: "anthropic-messages"`. Their rate-limit headers would overwrite the snapshot with non-Anthropic data.
- **Concurrent session race condition**: The `globalThis.fetch` wrapping has no reference counting or stack mechanism. When multiple sessions stream concurrently (possible across different lanes), install/uninstall ordering can corrupt the fetch chain, leaving stale hooks installed or silently removing active hooks.
- **Synchronous file I/O on hot path**: Uses `writeFileSync`/`renameSync` inside the async fetch wrapper, unlike the existing payload logger which uses an async `QueuedFileWriter`. This blocks the event loop on every API call.
<h3>Confidence Score: 2/5</h3>
- This PR has two logical issues (URL matching and concurrency) that should be addressed before merging.
- Score of 2 reflects: (1) the isAnthropicUrl function will match third-party Anthropic-compatible providers that exist in this codebase, writing incorrect rate-limit data; (2) the globalThis.fetch wrapping is not safe under concurrent sessions, which can corrupt the fetch chain; (3) synchronous file I/O is a less critical but notable deviation from established patterns. The core feature concept is sound but the implementation needs fixes.
- src/agents/anthropic-ratelimit.ts needs the most attention — both the URL matching logic and the fetch wrapping concurrency model need to be fixed.
<sub>Last reviewed commit: d8d8664</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#23700: feat: Claude CLI personal-use auth (no API key required) + native A...
by 88plug · 2026-02-22
78.0%
#10108: fix: override stale Anthropic OAuth stealth headers for Opus 4.6
by CivilBooks · 2026-02-06
77.3%
#21699: feat(agents): add opt-in OpenAI payload logging for embedded runs
by jcp · 2026-02-20
75.1%
#15397: feat: support Anthropic speed:"fast" parameter passthrough
by mode80 · 2026-02-13
74.0%
#10492: fix(auth): store Anthropic setup-token as type:oauth for auto-refresh
by sparck75 · 2026-02-06
73.3%
#13686: Add opt-in rate limiting and token-based budgets for external API c...
by ShresthSamyak · 2026-02-10
72.7%
#7821: feat: Support ANTHROPIC_BASE_URL environment variable for custom en...
by y1y2u3u4 · 2026-02-03
72.5%
#23720: Feat/cli backend runtime tuning
by wanmorebot · 2026-02-22
72.4%
#10006: feat: add append-only spend ledger for token usage tracking
by oldeucryptoboi · 2026-02-06
71.0%
#23573: feat: add programmatic tool calling (PTC) support for Anthropic
by syumpx · 2026-02-22
70.7%