Handle AI usage cap gracefully in studio code - #3102
Conversation
f61f949 to
38792ef
Compare
When the WordPress.com proxy returns the studio_cap_exceeded marker, detect it pre-flight via /studio-app/ai-assistant/quota and during a turn via the SDK's forwarded error body. Surface a friendly cap message with a /provider hint, suppress the trailing "Done" / SDK exit error, and fail fast by setting CLAUDE_CODE_MAX_RETRIES=0 so the agent doesn't hang through the SDK's default retry budget.
The pre-flight check via /studio-app/ai-assistant/quota only helps the already-capped case. Mid-turn caps — the realistic failure mode for long agent loops — are handled by the studio_cap_exceeded marker detection and CLAUDE_CODE_MAX_RETRIES=0 in the SDK env, so the pre-flight is just an extra round trip on every turn for no UX gain.
📊 Performance Test ResultsComparing f3acf5f vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
…-usage-cap-handling
Thanks for testing @nightnei! Yes, neither the long wait nor the error message are expected. From the error message, it seems that you have the quota set on the backend correctly, so this might be something on the CLI side. 🤔 Strange, because in my testing, I got ~10s turnaround time from my sandbox, although I can reproduce the second error message being shown: CleanShot.2026-04-21.at.10.53.12.mp4I'll investigate further and fix this. |
…ached Detect the cap via the SDK's "API Error: 429" text prefix (provider-gated to wpcom) so the detection survives backend body variations. Once the cap message has been surfaced, skip the generic "Claude Code process exited with code 1" error and the "hiccup on the server" retry prompt — retrying won't recover a cap.
|
@nightnei the error message detection drifted slightly between the backend and the cli changes, so you were essentially seeing the trunk behavior. I fixed it now, and now I get the error message as expected when following the testing steps in the PR description: CleanShot.2026-04-21.at.11.39.51.mp4Could you please give this another look? Thanks! |



Related issues
Related to STU-1555
Companion PR 211652-ghe-Automattic/wpcom
How AI was used in this PR
I used Claude to implement the changes and write tests. I reviewed and tested all changes manually.
Proposed Changes
When a user hit the AI usage cap while running
studio code, the agent would appear to hang for minutes before eventually showing a generic error. The cause is two non-obvious behaviors of@anthropic-ai/claude-agent-sdk:CLAUDE_CODE_MAX_RETRIES). Cap responses won't recover from a retry, so the user just waits through exponential backoff before seeing anything.ANTHROPIC_BASE_URLpoints atapi.anthropic.com. We point it at the WordPress.com proxy (https://public-api.wordpress.com/wpcom/v2/ai-api-proxy), so the SDK falls through toerror: 'unknown'regardless of what shape the upstream 429 body has. The response body is forwarded into the assistant message as a text block, butSDKAssistantMessage.errornever says'rate_limit'.This PR surfaces the cap and offers a path forward (
/providerto switch to a personal Anthropic API key):apps/cli/ai/providers.tssetsCLAUDE_CODE_MAX_RETRIES=0for the wpcom provider so the SDK stops retrying and returns control immediately.apps/cli/ai/ui.tsmatches astudio_cap_exceededmarker (set by the backend in the 4xx response body) inside assistant content text blocks. Since the SDK forwards the upstream body, this is the only reliable signal we get through a proxy. Picking a marker we control instead of pattern-matching the upstream JSON shape keeps the contract explicit and survives any SDK debug-output changes."Claude Code process exited with code 1"from the agent runner.handleAgentTurnErrorchecksui.hasErrorBeenSurfaced()and bails out so the user only sees one clear message. The "Done" / "Thought for Xs" indicators are also suppressed when the turn ended in a cap.I also experimented with a pre-flight quota check. It would only benefit the already-capped case, while mid-turn caps, the realistic failure mode for long agent loops where token budgets get debited as the turn runs, are handled entirely by the in-turn marker detection. Adding a per-turn round trip for a marginal UX gain wasn't worth it.
The companion PR 211652-ghe-Automattic/wpcom makes the marker show up in the response body.
Testing Instructions
curl 'https://public-api.wordpress.com/?amisandboxed'to confirm sandboxing is activenpm run cli:buildnode apps/cli/dist/cli/main.mjs code(and log in if needed)/provider, switch to "Anthropic · API key", paste a key, and confirm subsequent prompts workPre-merge Checklist