Anthropic Claude release April 2026, read through the 400 most guides miss
Opus 4.7 went GA on April 22. Everyone wrote that part up. The release cycle also included a refreshed consumer terms page, and every Claude client that sent a request on an un-reconsented session started getting a 400 that looks like auth and is not. This page walks through how Fazm handles that specific branch, with source.
Download Fazm for MacWhat the April 2026 release cycle actually shipped
Anthropic's April was heavy. Claude Opus 4.7 went generally available on April 22, 2026 at $5 input and $25 output per million tokens, with a 1M context window and materially better long-horizon coding. Claude Haiku 4.5 rolled out to 27 Amazon Bedrock regions. Claude Design launched as a research preview. Mythos Preview landed behind Project Glasswing. The Agent Client Protocol jumped from v0.25.0 to v0.29.2 in 13 days.
The piece nobody writes up is that Anthropic also refreshed their consumer terms in the same window. For users who had not logged in to claude.ai for a few weeks, the next OAuth-backed API call came back as an HTTP 400 invalid_request_error with a message telling them to accept the new terms at claude.ai. That specific error is the subject of this page.
It is the branch that does not fit anywhere else. It looks like auth. The recovery for auth is re-auth. Re-auth does not fix it. Every Claude-embedded client has to handle this one differently, or their users end up in a sign-in loop.
What the 400 looks like on the wire
Here is the response the ACP bridge actually surfaces to the Swift layer when a user's Claude session needs to re-accept terms. The status is 400 and the body is an invalid_request_error, which is the same shape an API client would get for a malformed field name. That collision is why a naive error handler sends users into an OAuth loop.
0 substring patterns. 0 lines of Swift. One dedicated branch ahead of every auth check.
The detector lives at Desktop/Sources/Providers/ChatProvider.swift line 1374. It takes the raw agent error message, lowercases it, and runs four substring checks. Any match returns true. The call site at line 2918 sits ahead of all three auth branches. That ordering is the whole trick.
Verify by grepping the open-source Fazm repo: grep -n isTermsAcceptanceRequired Desktop/Sources/Providers/ChatProvider.swift
The detector function, as shipped
Six lines of conditional substring matching. No regex. No JSON parsing. Anthropic does not ship a machine-readable error_type for terms-not-accepted, so the detection has to run on the text itself. The four phrasings below are the ones that showed up in actual 400 bodies Fazm observed during and after the April 22 GA window.
Why four patterns, not one
Each pattern catches a specific phrasing Anthropic uses in production. The detector is additive on purpose: if one phrasing changes, the other three still cover the case. If a fifth phrasing shows up, adding a new line to the function is the entire fix.
1. "consumer terms"
Fires on the Claude API's most common phrasing when a refreshed consumer terms page has not been accepted. Matches bodies like 'You must accept our updated Consumer Terms' in any case.
2. "terms of service" + "accept"
Fires on the formal phrasing some 400 bodies use. Both substrings must be present, which rules out generic mentions of 'terms' that do not actually require user action.
3. "terms and privacy"
Fires when Anthropic combines the two documents in a single sentence. This phrasing shows up when a refresh touches both surfaces at once, which was the April 2026 shape.
4. "updated our" + "policy"
Fires on policy-only refreshes that do not use the word terms. Usage policy changes can still gate account access, so the detector covers that vector too.
The flow when a match fires
The diagram below traces a single failed query through the error handler. The detector is node 2. If it fires, the query stops at node 3 and the verbatim 400 body is shown to the user. No retry, no re-auth, no bridge-mode switch. If it does not fire, the request falls through to node 4 where the auth and model-access branches take over.
Query error path, April 2026 detector in place
1. Query fails with BridgeError.agentError
The ACP bridge returns a structured agentError with a message string drawn from the Claude API's 400 body or the ACP SDK's translation layer.
2. isTermsAcceptanceRequired(msg) runs first
Lowercase and test four substring patterns. Ordering puts this ahead of every auth branch so a terms error never lands in an auth path.
3. Match: verbatim surface, no retry, done
pendingRetryMessage is cleared so no auto-resend happens. errorMessage is set to bridgeError.errorDescription which is the raw 400 body including the claude.ai link.
4. No match: fall through to auth and model branches
isAuthRelatedError for 401/unauthorized, isModelAccessError for 'may not exist' + 'not have access', then the generic fallback that shows localizedDescription.
5. User clicks Accept at claude.ai, next query works
No in-app prompt to reload. The next session/prompt runs on the same OAuth token that worked before, because the fix is on the Anthropic side.
What the branch actually does
The whole point of this branch is restraint. It has to resist the reflex to retry, resist the reflex to re-auth, and resist the reflex to wrap the error in a friendlier string. The user needs the original text with the claude.ai URL in it, so they know where to go.
When a terms match fires, the branch:
- Stops all retry attempts for this query (pendingRetryMessage = nil)
- Does not call switchBridgeMode() - mode stays where it is
- Does not set isClaudeAuthRequired - the auth sheet never opens
- Surfaces the 400 body verbatim via bridgeError.errorDescription
- Logs at info level with the current bridge mode for Sentry grouping
- Does nothing else - the fix is on claude.ai, not in Fazm
The five-branch triage tree, in source
For context, the terms branch sits inside a larger error handler that has exactly five branches. The snippet below lifts four of them from ChatProvider.swift lines 2918 through 2958. Reading them in order shows why the terms branch is where it is.
The inputs that feed the detector
Three upstream sources can produce the string that reaches isTermsAcceptanceRequired. Any one of them can trip a match. The diagram below shows how they converge on the detector, then fan out to the two possible outcomes.
Policy error flow, April 2026
How this branch came to exist
The timeline below walks through the April 2026 cycle as Fazm experienced it. The terms detector does not appear on day one because there was no cluster to detect yet. It appears on the same day Opus 4.7 goes GA, because that is when the cluster spikes loud enough to separate from general auth noise.
April 7 - ACP v0.25.0 lands
First Agent Client Protocol bump of the month. Adds improved error handling for credit exhaustion and rate limits. No terms-acceptance branch yet. The error surface is built around auth and rate limits only.
April 16 - Claude Opus 4.7 hits release candidate
Privacy language across Fazm onboarding is tightened in v2.3.2. Internal testing starts seeing sporadic 400 responses on claude.ai OAuth from accounts that have not logged in for months. The errors are initially clustered under generic auth failure in Sentry.
April 20 - ACP v0.29.2 and dynamic models
v2.4.0 ships. Available models now populate from the agent dynamically, so newly released Claude models appear without an app update. Free trial drops from 21 days to 1 day. Referrals launch with a $49 credit banner. The Sentry cluster on policy 400s grows as more users cycle through sign-in.
April 22 - Opus 4.7 GA, consumer terms refreshed, v2.4.1 ships
Opus 4.7 is generally available. Anthropic publishes a refreshed consumer terms page. The Sentry cluster spikes within hours. The OAuth-loop bug is reproduced internally against a fresh claude.ai account, the four substring patterns are verified against the live 400 body, and the terms branch is added ahead of the auth branches. The detector ships the same day.
April 23 - postmortem + this guide
The 2.4.1 release notes mention a sign-in retry fix. This guide documents the specific code change, the four substring patterns, and the ordering decision that keeps the terms branch ahead of the auth branches.
“The bridge-mode branches would have turned a terms refresh into an OAuth loop. Hoisting the terms detector above them is the entire fix.”
ChatProvider.swift comment at line 1371
Handling a policy-refresh 400
What a naive Claude client does vs. what Fazm's April 2026 triage does.
| Feature | Naive client | Fazm |
|---|---|---|
| Detects terms-not-accepted as its own error class | No - 400 is treated as generic auth failure | Yes - isTermsAcceptanceRequired runs before any auth branch |
| Avoids OAuth loop on policy refresh | No - re-auth retries until user gives up or rate limit hits | Yes - retry is explicitly disabled for this branch |
| Shows actionable claude.ai URL verbatim | No - generic 'Sign in again' message hides the real fix | Yes - errorMessage = bridgeError.errorDescription (raw body) |
| Works without a machine-readable error_type | No - relies on 401 or error_type=auth_error | Yes - four substring patterns against lowercased text |
| Fires in both built-in and personal modes | No - auth branches are mode-specific | Yes - terms branch sits ahead of mode-specific branches |
The four substring patterns are specific to phrasings Fazm has seen on the wire. The shape of the solution (detect, stop retry, surface verbatim) is the general lesson.
Why the naive fix is the wrong fix
The naive fix is to see a 400 from the Claude API, assume the cached OAuth token went bad, and trigger a fresh sign-in. That is how nearly every client SDK's default error handler behaves, because the 400 shape for an expired token looks almost identical to a terms-not-accepted 400.
Three things make it wrong. First, the token is not expired. Re-auth mints a new one that hits the same 400 on the first request. Second, the user cannot tell why sign-in keeps not sticking. They see a browser pop, a successful login, a return to the app, a 400. Third, the real fix lives on claude.ai. Until the user visits claude.ai/terms and clicks Accept, no client can fix this error. Hiding the message behind a generic 'sign in again' makes the fix invisible.
The Fazm branch does the opposite. It stops the retry loop, clears the queued message, and shows the 400 body unmodified. The body already contains the URL the user needs. That is all the user needs. The app does nothing further until the next query is sent, and by then the Anthropic side is fixed.
What this tells you about building on Claude
Anthropic's release cycle is about more than models and pricing. Policy refreshes, OAuth gateway changes, and terms updates all reach clients as errors that look like bugs in the client. The April 2026 cycle was the first one where a terms refresh and a GA model launch landed on the same day, and it produced a cluster of 400s that clients had to disambiguate from normal auth failure.
If you are building on the Anthropic API, the lesson is to treat the error space as bigger than the two categories the SDK hands you. Every class of error the user has to resolve out of band, at a URL, on a billing page, at a terms screen, has to be its own branch in your handler. Those branches do not retry. They surface verbatim. They stop the loop.
In Fazm's handler there are currently three such branches: terms acceptance, model access, and auth failure. The terms branch is the newest, shipped on April 22, 2026. When the next policy refresh happens, it is probably the one that catches it first.
Walk through the triage with us
Book a 20-minute call to see how Fazm handles Anthropic release cycles in production, the source included.
Frequently asked questions
What was actually in Anthropic's April 2026 Claude release?
The headline shipments were Claude Opus 4.7 GA on April 22, 2026 at $5 input and $25 output per million tokens, a 1M context window, materially better long-horizon coding, and a refreshed Claude Design surface. The Agent Client Protocol moved from v0.25.0 on April 7 to v0.29.2 on April 20. Claude Haiku 4.5 expanded to 27 AWS Bedrock regions. A Mythos Preview landed behind Project Glasswing. The piece nobody writes up is that Anthropic also refreshed their consumer terms during the same window, and every client that hits claude.ai for OAuth started getting 400 invalid_request_error responses until users went and clicked Accept.
Why does a terms update break a working app?
Because the 400 that Anthropic returns is shaped almost identically to an auth-failure 400. It is an invalid_request_error with a human-readable message pointing at claude.ai. A naive client sees a 400 from the Claude API and does the default thing: invalidate the cached token and trigger a fresh OAuth flow. That does not help. The user signs in again successfully, the token is minted, the next request still returns the same 400, the cycle repeats. The user lands on an OAuth loop. The correct behavior is to show the terms prompt verbatim and stop retrying until the user has clicked Accept at claude.ai.
Where exactly does Fazm detect this case?
In Desktop/Sources/Providers/ChatProvider.swift at line 1374, a static function called isTermsAcceptanceRequired takes the raw agent error message, lowercases it, and substring-matches against four patterns: consumer terms, terms of service combined with accept, terms and privacy, and updated our combined with policy. Any match returns true. The branch that consumes this match sits in the query error handler at line 2918, ahead of all three auth-error branches. That ordering is deliberate, because every one of those downstream branches would otherwise mis-triage the error into a re-auth flow.
Why four patterns and not one?
Because Anthropic does not ship a machine-readable error_type for terms-not-accepted. The detection has to be text-based. The four patterns cover the phrasings Fazm has actually seen on the wire from the Claude API, the claude.ai OAuth gateway, and the ACP SDK's error translation layer. Consumer terms is the most common phrasing when a Pro user hits a refreshed TOS page. Terms of service plus accept catches the more formal phrasing used in some 400 bodies. Terms and privacy covers the case where Anthropic combines the two documents in the message. Updated our plus policy catches policy-only refreshes that do not mention terms at all. Four patterns in one six-line function.
Why does re-triggering OAuth make things worse?
Three reasons. First, user experience: an OAuth loop with no explanation is the exact shape of an app that looks broken. The user does not know what to fix, so they quit and complain. Second, rate limits: Anthropic's OAuth callback endpoint is itself rate-limited, and repeated sign-in attempts from the same client IP in a short window can trip that limit, which makes recovery slower once the user does accept the terms. Third, cached tokens: some client libraries refresh the token preemptively on 400, which means the retry path actively throws away a valid token in exchange for nothing. Showing the verbatim message is cheaper and correct.
Does this affect users on Fazm's built-in Claude account, or only users on their own Claude Pro account?
It affects both, but for different reasons. Users on Fazm's built-in account are signed in as a Fazm-managed Vertex AI identity, not as themselves. They still occasionally see a 400 from Claude if Anthropic refreshes the Vertex AI enterprise terms, which is why the detector is also wired into the built-in branch. Users on their own Claude Pro or Max account see it whenever claude.ai ships a TOS update and their OAuth session has not been re-consented yet. The detector fires in both modes and the error is surfaced the same way: verbatim text, no retry, no bridge-mode switch.
What other error branches sit alongside this one?
The query error handler between lines 2918 and 2958 has exactly five branches. First, terms acceptance required: show verbatim. Second, built-in auth failure: switch bridge mode to personal, trigger sign-in. Third, personal OAuth auth failure: trigger re-sign-in in place. Fourth, personal mode model access error: switch to built-in and auto-retry the query. Fifth, generic fallback: show the localized error description. Each branch is a specific signal that came out of a real production incident. The terms branch is the only one that actively refuses to retry.
Why was this branch added during the April 2026 cycle specifically?
Because the April 2026 cycle was the first time Anthropic pushed a consumer terms refresh on the same weekend as a GA model launch. Prior terms updates landed on quiet days and did not pile up with a traffic spike. When Opus 4.7 went GA on April 22 and a noticeable fraction of users signed in for the first time in weeks, the OAuth gateway started returning terms-not-accepted 400s at a rate that was not small. Fazm's Sentry clustered the errors under auth-failure for a day, which is how the bug was found, and the detector was added in the 2.4.1 release that shipped the same day.
What happens after the user clicks Accept at claude.ai?
Nothing in the Fazm client needs to change. The next query goes out on the already-valid OAuth token, claude.ai sees that consent has been recorded against the user account, the 400 no longer fires, and the agent responds normally. There is no in-app prompt to reload or re-sign-in because none is needed. That is the whole reason this branch is a dead-end instead of a retry: the fix is on the Anthropic side, not in the client.
Is any of this specific to Fazm's architecture, or does every Claude client need it?
Every client that embeds Claude needs an equivalent branch. The specific substrings are specific to the phrasings Fazm has seen, but the pattern is universal: any time an upstream API returns a 400 that the user has to resolve out of band (in a browser, on a billing page, at a terms screen), the client cannot paper over it with a retry. The cheapest correct behavior is to detect the class, show the text, stop the loop. If you are building on the Anthropic API and you do not have a branch like this, your next policy refresh will put your users into an OAuth loop until you ship a fix.
Three more April 2026 Claude-release pages, each one built around a single concrete code path in the Fazm repo.
Keep reading
Anthropic Claude new model April 2026
Opus 4.7 shipped under the slug default on ACP v0.29, which broke Fazm's saved Smart preference in two specific ways. The migration code and the two patches it took to fix it.
Anthropic Claude release notes April 2026
Nine Fazm releases in 20 days of April, each one tagged to the specific upstream Anthropic or Claude-side change that forced it. A downstream shipping log.
Anthropic Claude AI news April 2026
The Claude Code Pro removal test, a stretch of admitted degraded quality, and why Fazm's $10 built-in Vertex AI budget plus a bidirectional failover absorbed almost none of it.