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.

Sonnet 4.6Opus 4.7 GA 2026-04-22ACP 0.29.2Fazm 2.0.1 to 2.4.0
M
Matthew Diakonov
12 min read
4.7from Sourced from the Fazm repo across 10 April 2026 releases
10 tagged releases, April 3 through April 20
Every guard tied to a file and line number
No announcements recycled from Anthropic's blog

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.

1 loop

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.

Desktop/Sources/FloatingControlBar/ChatQueryLifecycle.swift

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

Anthropic
ACP SDK
Claude Code
Fazm ACP bridge
ShortcutSettings.swift
Chat header picker
Floating bar

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

UserFloating barACP bridgeAnthropictypes a promptsession/promptClaude call429 usage limit reachederrorMessage: 'usage limit'ChatQueryLifecycle line 68 matchesUpgrade button renders, no crash

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.

fazm dev log — user-visible strings

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.

1

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.

2

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.

3

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.

4

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.

5

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.

6

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.

7

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.

8

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

0tagged Fazm releases in April 2026
0silent failure modes caught
0 rowssubstrings in the model family map
0 tokensin the rate-limit guard

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

FeatureFazm 2.0.1Fazm 2.4.0
New Claude model ID arrivesNeeds an app release, users waitAppears in the picker on next session/new
Personal plan rate-limit errorCrash loop on repeat, app restartsUpgrade button renders, chat stays open
Selected model blocked by planStream fails silently, blank bubbleAuto-fallback to built-in, one-line notice
Built-in credits exhaustedGeneric error, no CTAConnect Claude button, OAuth flow starts
OAuth token expires mid-streamFloating bar gets stuck on 'Something went wrong'Re-prompts sign-in, preserves session context
ACP 0.25.0 → 0.29.2 error shape changeSwift code breaks on new payloadBridge 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.
ChatQueryLifecycle.swift:68emitModelsIfChanged:1271ShortcutSettings.updateModelscheckForCrashLoop:138AnalyticsManager.creditExhaustedbridgeMode == "personal"models_available eventACP 0.29.2

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.