Field notes from a shipping Mac agent
Anthropic Claude updates in April 2026, absorbed line by line inside a consumer Mac app
Every roundup of Anthropic's April 2026 changes is a recap of press releases. This page is a recap of the downstream error paths, the file names, and the line numbers a shipping Mac agent touched to keep Claude working for users while the surface kept moving underneath.
The short version, for anyone shipping on Claude
April 2026 did not give downstream apps a single big thing to react to. It gave them a steady drip of small changes at the edges: a new model ID here, a reworded error message there, a protocol bump, a renamed field in the credit-exhaustion payload. Each one is easy to miss on its own. Together, they are enough to break a consumer app that has not thought about its error surface in a while.
The rest of this page is the list of what Fazm, a Mac desktop agent, shipped in response. Ten tagged releases between April 3 and April 20, each one addressing a specific downstream symptom. We wrote them down so other app teams can compare notes, and so users searching this topic can see what one shipping consumer app actually does about these releases, not what we say we do.
“Repeated rate-limit failures caused a crash loop that only fired for a single user. The fix was a two-string guard in ChatQueryLifecycle.swift.”
Fazm 2.2.0 release notes, April 11, 2026
The anchor fact: two substrings, one bridge mode
If you only read one section of this page, read this one. The smallest guard in the codebase absorbs the largest share of Anthropic's April 2026 rate-limit changes.
That is the whole guard, on lines 67 through 74. Two substring matches against the error text, one equality check on bridge mode, one published flag that the floating bar reads to render an Upgrade button. Before this branch existed, the founder chat would crash on the third repeated rate-limit error and the app would enter a restart loop. Fazm 2.2.0, shipped April 11, 2026, added exactly these seven lines. Every downstream Anthropic rate-limit phrasing change since has to survive this pair of substring tests before it reaches a user.
Why it holds
Anthropic has changed the exact wording of rate-limit errors multiple times since 2024. Hardcoding a specific phrase would have broken the guard on the first shift. Matching on both “usage limit” and “rate limit” covers the two phrasings the edge has alternated between, and the bridgeMode check makes sure we only offer the Upgrade CTA when the user is on their own plan. The built-in Fazm plan gets a different CTA path because it routes to OAuth, not billing.
How the update reaches a user, end to end
April 22, 2026 — the morning Opus 4.7 went GA
The bridge at acp-bridge/src/index.ts line 1271 receives the fresh model list from the ACP SDK, drops the “default” pseudo-entry, compares against lastEmittedModelsJson, and streams a models_available event to the Swift app. On the Swift side, ShortcutSettings.updateModels at lines 178 to 212 runs each ID through a three-row substring map (haiku, sonnet, opus) and pushes the result into the observable availableModels array. The picker rerenders. The user never sees a version number.
The six silent failure modes
One card per failure mode. Each one is real, each one has a corresponding release note in the Fazm repo, each one has a file and line number where the catch lives.
New model ID arrives mid-session
Anthropic publishes claude-opus-4-7-20260422. The ACP SDK reports it in availableModels on the next session/new. Without a guard, the Swift picker would still show the old hardcoded list.
Rate limit error phrasing shifts
Edge returns 'daily usage limit reached' instead of the old 'rate limit exceeded'. A naive exact-match check would miss it and fall through to the generic error branch.
Personal Claude plan denies selected model
User picked Opus, their plan covers Sonnet only. Without a fallback, the stream fails silently with no UI feedback. The April 5 fix switches them to built-in automatically.
OAuth expires mid-conversation
Token refresh window lapses while the user is typing. The floating bar needs a Connect Claude button, not a thrown error, and the session context has to survive the reconnect.
Credit exhaustion between turns
Fazm-managed credits drain on the last turn. The next turn errors immediately. The user sees 'Your free built-in credits have run out. Connect your Claude account to continue.' not a stack trace.
ACP protocol bump
0.25.0 on April 7, 0.29.2 on April 20. Error shapes change. The Swift side should not care, so the bridge at acp-bridge/src/index.ts normalizes both versions into the same send() payloads.
The conversation flow that rate-limit guard enables
personal Claude plan hits usage limit mid-stream
What the user sees when a guard fires
Each error branch in ChatQueryLifecycle produces a specific, user-readable string. No stack traces. No raw provider payloads. Here are the four strings that users actually encountered at least once during April 2026, in the order they appear in code.
The April 2026 release timeline, inside one Mac agent
Ten tagged releases between April 3 and April 20, 2026. Each one closes a gap that an Anthropic Claude update opened, or tightens a guard that was already there.
April 3 — Fazm 2.0.1 lands
First post-reorg release. Baseline. The error handling branches that later absorb every April change are already in ChatQueryLifecycle but have only two named guards so far.
April 4 — Chat observer cards auto-accept
2.0.6 flips the observer cards from opt-in approval to auto-accepted with an undo. Also fixes the bug where the AI agent would accidentally type its own reasoning into user documents when controlling desktop apps.
April 5 — Personal-plan model fallback
2.0.7 adds the auto-fallback to built-in account when the personal Claude plan lacks access to the selected model. The chat used to fail silently here. Now the user sees a one-line notice and keeps going.
April 7 — ACP 0.25.0
2.1.2 upgrades the ACP protocol and rewires credit exhaustion and rate-limit error handling. Onboarding no longer silently fails when credits run out: it shows an explicit error with a Connect Claude option.
April 9 — Manage Subscription
2.1.3 adds a Manage Subscription button in Settings for Fazm Pro subscribers. Error messages now show in the pop-out chat window, not just the main one. A small fix that closes a specific reporting gap.
April 11 — Rate-limit crash-loop fix
2.2.0 fixes the founder chat crash loop on repeated rate-limit failures. This is the release that introduced the two-substring guard at ChatQueryLifecycle.swift line 68 that is still load-bearing today.
April 16 — Privacy language
2.3.2 tightens onboarding and system prompt language to say local-first instead of nothing leaves your device. Honest relabeling, driven by the fact that the observer session does round-trip to Anthropic for inference.
April 20 — Dynamic model list
2.4.0 makes available models populate from the agent. Adds custom MCP server support via ~/.fazm/mcp-servers.json. Upgrades ACP to 0.29.2. Paywall now triggers after 3 messages, trial shortens from 21 days to 1.
What changed, counted
The two-token rate-limit guard is two string literals: “usage limit” and “rate limit”. The three-row family map is the hardcoded list [“haiku”, “sonnet”, “opus”] in ShortcutSettings.swift line 159. Small structures, load-bearing outcomes.
Before the guards, after the guards
| Feature | Fazm 2.0.1 | Fazm 2.4.0 |
|---|---|---|
| New Claude model ID arrives | Needs an app release, users wait | Appears in the picker on next session/new |
| Personal plan rate-limit error | Crash loop on repeat, app restarts | Upgrade button renders, chat stays open |
| Selected model blocked by plan | Stream fails silently, blank bubble | Auto-fallback to built-in, one-line notice |
| Built-in credits exhausted | Generic error, no CTA | Connect Claude button, OAuth flow starts |
| OAuth token expires mid-stream | Floating bar gets stuck on 'Something went wrong' | Re-prompts sign-in, preserves session context |
| ACP 0.25.0 → 0.29.2 error shape change | Swift code breaks on new payload | Bridge normalizes, Swift path unchanged |
The checklist other shipping apps can borrow
If you are building a consumer product on Claude and want to re-audit your error surface after this month's updates, this is the list Fazm now keeps pinned next to the release checklist.
Post-Anthropic-release audit
- Every rate-limit string your app depends on is a substring match, not an exact match.
- Your error branch explicitly distinguishes credit exhaustion from rate limit and maps to a different CTA.
- Your model picker is populated from the live API response, not a hardcoded enum in your own repo.
- You have a fallback path when the user's selected model is blocked by their plan.
- You have an OAuth re-prompt branch that preserves in-flight session context, not one that drops the conversation.
- You have a crash-loop guard that runs before any other init and can roll back the last update.
- You have a logged fallback string for when none of the above branches match, and treat hits on it as P1.
One ticker, to make it concrete
Fazm shipped 0 tagged releases between April 3 and April 20, 2026. Every one of them was publicly available through the auto-updater without a user action beyond restarting the app. Every one of them carried at least one entry in the Fazm CHANGELOG that traces to an Anthropic Claude-side change earlier that week.
Want to see the six guards running live on a Mac?
Book 20 minutes. We will screenshare a Fazm session with /tmp/fazm-dev.log tailing next to it, trigger a rate-limit error on purpose, and walk you through each branch as it fires.
Book a call →Questions
What changed about Anthropic Claude in April 2026?
Two headline releases and a pile of smaller surface changes. Claude Sonnet 4.6 stayed the everyday default for speed-sensitive workloads. Claude Opus 4.7 went GA on April 22, 2026 at $5 input and $25 output per million tokens. Alongside the models, the ACP (Agent Client Protocol) SDK pushed through 0.25.0 on April 7 and 0.29.2 on April 20, rate-limit error strings shifted wording, and the OAuth refresh flow behaved differently when a personal Claude account lacked access to a specific model. Each of these is a line item in someone's changelog. Inside a consumer Mac agent like Fazm, they are six different places the app had to put a named guard so a user never sees a raw Anthropic error bubble up.
Where is the exact code that catches a Claude rate limit and shows an upgrade button?
Desktop/Sources/FloatingControlBar/ChatQueryLifecycle.swift at lines 68 to 74. The check is two substring tests: errorText.contains("usage limit") or errorText.contains("rate limit"). If both are true and bridgeMode equals "personal", Fazm sets state.showUpgradeClaudeButton, which renders the upgrade CTA in the chat footer instead of a scary stack trace. Before this check existed, repeated rate-limit failures at the Anthropic edge caused the founder chat to enter a crash loop. That specific bug is the one fixed by version 2.2.0 on April 11, 2026.
How does Fazm handle a new Claude model showing up mid-month?
Via a narrow two-step dance. First, acp-bridge/src/index.ts at line 1271 defines emitModelsIfChanged, which receives availableModels from the ACP SDK on every session/new, filters out the "default" pseudo-model, deduplicates against lastEmittedModelsJson, and sends a models_available message over the Unix socket to the Swift app. Second, Desktop/Sources/FloatingControlBar/ShortcutSettings.swift at lines 178 to 212 maps each incoming model ID against a three-row substring table (haiku, sonnet, opus) and relabels it Scary, Fast, or Smart. When Anthropic published Opus 4.7 on April 22, 2026, this path picked it up on the next session warmup without an app rebuild. The 2.4.0 release notes call this out as: available AI models now populate dynamically from the agent, so newly released Claude models appear without an app update.
Does Fazm still work if a user's personal Claude plan does not have access to the selected model?
Yes. The fix landed in 2.0.7 on April 5, 2026 with the entry: fixed chat failing silently when personal Claude account lacks access to the selected model, now auto-falls back to built-in account. The built-in account is a Fazm-managed Vertex AI Claude instance that was added earlier as a first-launch fallback. The flow is: ChatQueryLifecycle sees the error, ChatProvider clears showCreditExhaustedAlert, a new session is spun up against built-in, and the user sees a one-line notice that the switch happened instead of a failed stream.
What is the crash-loop guard that runs before anything else?
Desktop/Sources/FazmApp.swift at line 138 calls UpdateRollbackManager.checkForCrashLoop() as the very first line of init, before any view model, provider, or window is constructed. The comment on line 136 states explicitly: crash-loop detection must run before ANY other init. The counter is reset to zero only after a full successful launch, at line 435 in the same file. Without this, a bad Claude error path that blows up during provider init would repeat the same crash every time the user tries to reopen the app. With it, three consecutive crashes trigger an automatic rollback to the last good build. This is a structural guard, not a fix for a specific April release, but it is what made the mid-month Claude updates safe to ship aggressively.
Why do credit exhaustion and rate limits show up separately in Fazm?
They have different user intents. Credit exhaustion (built-in Fazm-managed credits ran out) needs a Connect Claude CTA that kicks off OAuth to the user's personal claude.ai account. Rate limit (user's own Claude plan hit usage caps) needs an Upgrade CTA that points to their Anthropic billing page. Desktop/Sources/AnalyticsManager.swift tracks them separately via creditExhausted at line 1000 and rateLimitEvent at line 1007, and ChatQueryLifecycle branches on them at lines 57 to 74. Collapsing the two into a single error banner would send users to the wrong page half the time.
Did Anthropic change rate-limit error wording in April 2026, and how did Fazm adapt?
Yes, subtly. The SDK-level rate-limit messages shifted to the strings now matched at ChatQueryLifecycle.swift line 68, which guards on both "usage limit" and "rate limit" because different endpoints phrase it differently. The matching pair on the ACP bridge side was upgraded in version 2.1.2 on April 7, 2026 with the entry: upgraded ACP protocol to v0.25.0 with improved error handling for credit exhaustion and rate limits. The final ACP bump to 0.29.2 shipped with 2.4.0 on April 20, 2026. Each bump tightened what the bridge forwards to Swift, so the two-substring check stayed stable.
What did Fazm change about privacy language in April 2026?
The 2.3.2 release on April 16, 2026 has the entry: tightened privacy language in onboarding and system prompts to accurately say "local-first" instead of "nothing leaves your device". The reason is honest product work. Fazm runs a second Claude session in the background for memory writing, and those turns do go to Anthropic for inference, even though the resulting files live on the user's disk. Saying "nothing leaves your device" was technically inaccurate the moment that session was added. Fazm swapped it to "local-first" in the onboarding copy and in every relevant system prompt string under Desktop/Sources/Chat/ChatPrompts.swift in the same release.
Is Claude Opus 4.7 selectable in Fazm today?
Yes. On April 22, 2026 GA day, Opus 4.7 was available in the Smart slot of the model picker without a new Fazm release. The Scary slot maps to the latest Haiku, Fast to the latest Sonnet, Smart to the latest Opus. The substring match is in ShortcutSettings.swift at line 159, the family map literal is at lines 160 to 163. When you open the chat header in the floating bar and switch to Smart, Fazm passes the full Opus model ID back through ACP and the next turn runs on Opus 4.7.
If something does slip through, what does the user actually see?
A fallback message at ChatQueryLifecycle.swift line 90: "Failed to get a response. Please try again." This is the last-resort branch, reached only when none of the named guards (auth required, credits exhausted, rate limit, paywall, browser setup pending) applied and no partial response was streamed. Seeing this message in production is a direct signal that a new Claude error mode slipped past the substring checks. We treat it as a P1 trigger to add a new branch above it. The file at line 67 is where that new check would slot in.
Other corners of the Anthropic release written up from inside a shipping Mac agent
More on April 2026
Anthropic new model April 2026
The three-row substring map that relabels Claude model IDs as Scary, Fast, and Smart, so Opus 4.7 lands without an app release.
Anthropic Claude latest model April 2026
What it looks like to run a second Claude Sonnet 4.6 session in the background for memory writing while the user chats with the first.
Anthropic April 2026 news
The single function and the single JSON file that let a shipping Mac app absorb April 2026 Anthropic news with zero user-facing app updates.