From e00800fc89e761382326c3a5eef8f4cccfbbc205 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:44:52 -0700 Subject: [PATCH] feat(classifier): Anthropic-specific guidance for subscription exhaustion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an Anthropic Claude Pro/Max OAuth subscription hits the "out of extra usage" 400 (now classified as billing), surface actionable guidance pointing at claude.ai/settings/usage and the cycle-reset option instead of the generic "add credits with that provider" line — which does not apply to a subscription. Folds in the UX from #40073 (@harsh-matchmyflight) without the extra FailoverReason enum; the billing reclass already provides the recovery behavior. --- agent/conversation_loop.py | 20 ++++++++ .../agent/test_anthropic_billing_guidance.py | 46 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/agent/test_anthropic_billing_guidance.py diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index e451c43cb..b5c970420 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -205,6 +205,26 @@ def _billing_or_entitlement_message( provider_label = (provider or "").strip() or "the selected provider" model_label = (model or "").strip() or "the selected model" + + # Anthropic Claude Pro/Max OAuth subscriptions surface exhaustion of the + # metered "extra usage" bucket as a hard 400 ("You're out of extra + # usage"). Point at the exact settings page and note the cycle-reset + # option, since the generic "add credits with that provider" line doesn't + # apply to a subscription — the user waits for the reset or switches to an + # API key. + if (provider or "").strip().lower() == "anthropic": + lines = [ + ( + f"{provider_label} reported that your Claude subscription usage is " + f"exhausted for {model_label} (included quota + extra-usage credits)." + ), + "Options: wait for the billing cycle to reset, or add extra usage at " + "https://claude.ai/settings/usage", + "You can also switch to an Anthropic API key or another provider with " + "/model --provider .", + ] + return "\n".join(lines) + lines = [ ( f"{provider_label} reported that billing, credits, or account " diff --git a/tests/agent/test_anthropic_billing_guidance.py b/tests/agent/test_anthropic_billing_guidance.py new file mode 100644 index 000000000..142b8b04c --- /dev/null +++ b/tests/agent/test_anthropic_billing_guidance.py @@ -0,0 +1,46 @@ +"""Tests for the Anthropic-subscription branch of +``agent.conversation_loop._billing_or_entitlement_message``. + +Regression context: Anthropic Claude Pro/Max OAuth subscriptions surface +exhaustion of the metered "extra usage" bucket as a hard HTTP 400 +("You're out of extra usage. Add more at claude.ai/settings/usage..."), +which classifies as ``FailoverReason.billing``. The generic billing +guidance ("add credits with that provider") is wrong for a subscription — +the user waits for the cycle reset or switches to an API key. This branch +gives Anthropic-specific, actionable guidance (folds in PR #40073's UX). +""" +from __future__ import annotations + +from agent.conversation_loop import _billing_or_entitlement_message + + +def test_anthropic_subscription_exhausted_guidance(): + """Anthropic billing guidance points at the exact settings page and + the cycle-reset option, not the generic 'add credits' line.""" + msg = _billing_or_entitlement_message( + capability="model access", + provider="anthropic", + base_url="https://api.anthropic.com", + model="claude-opus-4-7", + ) + assert "claude.ai/settings/usage" in msg + # Must mention the subscription cycle reset (not generic 'add credits'). + assert "reset" in msg.lower() + # Must still offer the provider-switch escape hatch. + assert "/model" in msg + # Model name should be interpolated. + assert "claude-opus-4-7" in msg + + +def test_non_anthropic_billing_guidance_unaffected(): + """A non-Anthropic provider keeps the generic billing guidance and does + NOT get the Anthropic-specific claude.ai settings link.""" + msg = _billing_or_entitlement_message( + capability="model access", + provider="openrouter", + base_url="https://openrouter.ai/api/v1", + model="anthropic/claude-opus-4.7", + ) + assert "claude.ai/settings/usage" not in msg + # Generic path still surfaces the OpenRouter credits link. + assert "openrouter.ai/settings/credits" in msg