Field notes from a Mac agent built on Claude

Anthropic's new Claude model in April 2026, traced through the single SDK rename that broke a Mac app's model picker

The new model is Opus 4.7. The interesting part is not the model. It is the rename: the Agent Client Protocol SDK started reporting Opus 4.7 under the slug default, and that one rename produced a real preference-loss bug inside Fazm that took two patch releases to fully repair. This page is the walkthrough.

Opus 4.7 GA 2026-04-22ACP SDK v0.29Fazm 2.4.0 to 2.4.1 + unreleasedShortcutSettings.swift line 174
M
Matthew Diakonov
9 min read
4.7from Sourced from the Fazm repo on the day Opus 4.7 went GA
Every claim anchored to a Fazm file path and line number
Cross-referenced against the live CHANGELOG.json in the repo
Written from inside the ACP bridge code, not a press recap

The short version

On April 22, 2026, Claude Opus 4.7 went generally available. Most coverage of this release is about the model: the price tag, the benchmark deltas, the new tool-use behavior. None of that is the interesting story for a downstream consumer app. The interesting story is what shipped on the SDK side. The Anthropic-published Agent Client Protocol SDK started reporting Opus 4.7 under the model ID default in version 0.29. Fazm, a Mac agent that runs on top of Claude over this SDK, treats default as a pseudo-model and filters it out before publishing the picker list to its Swift settings layer. That filter is correct. What it collided with is the preference migration on the Swift side, which assumed a saved opus would normalize forward to default. The result was a saved preference that matched nothing in the new picker list.

The fix was small. The lesson is bigger. A new Anthropic model is not just a new model; it is a new identifier that has to travel through every persistence layer that ever stored the old one. The rest of this page is the line-by-line trace of that journey through Fazm's codebase.

line 174

ACP SDK v0.29+ uses 'default' for Opus 4.7; migrate stored 'opus' to match.

Desktop/Sources/FloatingControlBar/ShortcutSettings.swift, line 174

0Opus model GA April 22
0ACP SDK version that renamed it
0Fazm releases involved in the fix
0Conflicting comments on the same bus

Anchor fact

Two comments, two processes, one rename

Fazm runs as two processes. The macOS app is Swift; the agent bridge is a Node script that hosts the Anthropic ACP SDK. They talk over JSON-RPC on stdio. Both ship in the same Fazm app bundle. On launch day for Opus 4.7, they were carrying opposite assumptions about a single string. Both comments are real, both are still in the repo, and both are correct in isolation. The mismatch is what produced the bug.

What `default` means in two parts of the same app

// line 1271-1281
function emitModelsIfChanged(
  availableModels: Array<{
    modelId: string;
    name: string;
    description?: string;
  }>
): void {
  // Filter out the "default" pseudo-model
  // it's not a real selectable model
  const filtered = availableModels
    .filter(m => m.modelId !== "default");
}
18% fewer lines

Read the two side by side. The Node bridge filters default out of the published model list because Anthropic publishes it alongside the real, version-suffixed Opus 4.7 ID and the picker should only show the real one. The Swift normalizer rewrites a stored opus to default because that is what ACP started using. Both edits are reasonable on their own. Together they produce a saved preference that lives on the Swift side as default and an available model list on the same Swift side that no longer contains default at all, because the bridge stripped it.

How the rename moves through the stack

The model ID's journey, drawn out

The rename does not arrive at one place. It crosses four. Each arrow below is a real call site. The error arrow is the broken path; the response arrow at the bottom is the unreleased fix.

ACP SDK -> bridge -> ShortcutSettings -> picker

Anthropic SDKACP bridgeShortcutSettingsPicker UIsession/new -> result.models.availableModelsfilter out modelId === 'default'send {type:'models_available', models}updateModels(acpModels) walks family map@Published var availableModelssaved 'opus' -> normalize -> 'default' (broken)fix: 'opus' -> recovery branch finds real ID

The pipeline, one diagram

Where Opus 4.7 enters the app

The hub here is the ACP bridge, the only Fazm process that speaks to Anthropic's SDK. Inputs on the left are model list emitters. Outputs on the right are the consumers that depend on the published list being correct. When the rename collides with a saved preference, the breakage shows up at the picker on the far right.

The model list bus

session/new
session/set_model
models_available
ACP bridge
ShortcutSettings
Floating bar picker
Saved preference

The migration code, exactly as it ships

The thirteen lines that absorb a model rename

This is the family map and the migration. Read line 159 first: a four-row table that pairs a substring against a label and an order. Then read line 197 onward: the recovery branch that tries to map a stale saved preference against the new list before giving up. The unreleased fix lives inside this same window.

Desktop/Sources/FloatingControlBar/ShortcutSettings.swift

The recovery branch is the part that has to change. Today it asks for the saved string to be a substring of some published model ID. With default, no published ID contains that substring (the bridge filtered it out). With opus, the published claude-opus-4-7-... ID matches and the upgrade lands. So the unreleased fix is to stop normalizing opusdefault in step one and let the substring-upgrade in step two do the work it was already designed for.

Real bridge output

What the ACP bridge actually emits on launch day

This is the log the bridge writes to stderr after a freshsession/newon April 22, 2026. The first line is the raw payload from the ACP SDK. The second is what gets forwarded to Swift. The third is the Swift side's log entry after walking the family map. The mismatch is between the second and third lines.

ACP bridge stderr -> Fazm app log

Three releases, in order

What landed when

The interesting thing about this story is that the work to absorb Opus 4.7 was distributed across three releases. The forward-compatibility hook was already in 2.4.0, two days before the model existed publicly. The label fix shipped on GA day. The full preference fix is queued for the next patch.

1

April 20, 2026 — Fazm 2.4.0 ships dynamic model loading

The changelog reads `Available AI models now populate dynamically from the agent, so newly released Claude models appear without an app update`. Under the hood, ChatProvider.swift line 1016 wires `acpBridge.setModelsAvailableHandler` to `ShortcutSettings.shared.updateModels`, and the family map at ShortcutSettings.swift line 159 gets a fourth row mapping `default` to the same Smart label as `opus`. This is the forward compatibility hook for an Opus release that has not landed yet.

2

April 22, 2026 — Opus 4.7 generally available

Anthropic ships Claude Opus 4.7 to direct API customers, Vertex AI, and the Agent Client Protocol SDK. Pricing is $5 per million input tokens and $25 per million output tokens. The ACP SDK reports the new model under the slug `default` alongside the real, version-suffixed model ID. Fazm's bridge process filters the `default` row out at acp-bridge/src/index.ts line 1274 because it is treated as a pseudo-model.

3

April 22, 2026 — Fazm 2.4.1 patches the surface label

Same day as Opus 4.7 GA. The fix is `Fixed model label showing 'Smart' for Sonnet users when Anthropic reports a partial model list`, anchored to the chained fallback in ShortcutSettings.swift line 222. This stops the chat header from claiming the active model is Smart when the active session is actually running on Sonnet. It does not fix the deeper preference path.

4

Unreleased — preference persistence fix queued

The CHANGELOG.json `unreleased` entry, written into the repo on April 23, reads `Fixed Smart (Opus) model preference not persisting after app update — now correctly maps stored 'opus' to the new ACP model ID`. The change rewrites the substring rule in `normalizeModelId` so a saved `opus` resolves to a real Opus 4.7 model ID through the recovery branch at ShortcutSettings.swift line 208 instead of collapsing to a filtered `default`.

The label resolution that stopped Sonnet from looking like Smart

The chained fallback at line 222

Fazm 2.4.1 specifically fixed the label rendering. The bug before this fix: when ACP reported a partial model list (for example, Sonnet only because Opus was rate-limited on the user's personal Claude account), the chat header still rendered Smart because the saved preference was Smart. The fix is the chained fallback below: resolve the label through the live picker first, then the static defaults, then the normalized alias, and only fall back to Fast if all three miss.

Desktop/Sources/FloatingControlBar/ShortcutSettings.swift

A consumer comparison

What a generic Claude wrapper does vs. what Fazm does

The cost of getting model IDs wrong is not theoretical. It is a paying user opening the app on a Wednesday morning, picking Smart, and getting a Sonnet response with no signal that the picker silently dropped them down a tier. The right column below is the part of Fazm that exists because that scenario is unacceptable.

FeatureGeneric Claude wrapperFazm
Anthropic ships Opus 4.7 mid-versionApp needs a new release with the new model ID hard-coded into the pickerChatProvider.swift line 1016 receives a `models_available` message and rebuilds the picker on the main actor
ACP SDK reports the model under a `default` pseudo-slugPicker shows `default` as a real selectable item, confusing the useracp-bridge/src/index.ts line 1274 filters `default` out before emitting to Swift
User had `opus` saved as their Smart preferenceSaved preference does not match the new model ID, picker silently drops to default modelRecovery branch at ShortcutSettings.swift line 208 walks the new list looking for a substring match before giving up
Chat header label needs to follow the live session, not the saved preferenceHeader still says Smart while session is actually running SonnetChained fallback at ShortcutSettings.swift line 222 resolves the live label through `availableModels`, then `defaultModels`, then normalized alias
Anthropic renames a model ID at the SDK boundary againAnother release ships, another picker hard-code, another partial state for old installsAdd one substring row to the family map at ShortcutSettings.swift line 159 and the picker absorbs it without an app update

A fact this product can claim

Substring rows in the family map

0

That is the entire surface area Anthropic gets to rename before Fazm has to ship a release. Add one substring and one label to the table at ShortcutSettings.swift line 159 and the picker absorbs the next model name.

What a Fazm user did not see on April 22, 2026

Concrete non-events

Every item below is something a generic Claude consumer could plausibly have seen on Opus 4.7 GA day. None of them appear on a Fazm user's screen.

Off-screen, by design

  • A new Fazm release shipped on April 22, 2026 specifically to add Opus 4.7 to the picker
  • A modal asking the user to choose between the old and new Opus
  • A `default` row appearing in the chat model picker
  • A direct Anthropic API call from Swift on the chat path
  • A hard-coded `claude-opus-4-7` string anywhere in the desktop binary
  • A picker that silently changed the active model from Smart to Fast for the user

Other places this rename echoed

Adjacent surfaces that stayed quiet

session/set_model accepts the renamed IDwarmupSession seeds Sonnet 4.6, no churnSmart pill renders Opus labelCumulative cost still tracked per queryBuilt-in $10 cap unaffectedPersonal claude.ai OAuth still worksKnowledge graph nodes unchangedVoice TTS toggle unchangedCustom MCP servers unchangedDetached chat windows unchanged

Honest caveats

Where this story stops

The model rename is not the only April 2026 Anthropic shift, and the family map is not a silver bullet. If a future Anthropic release introduces a brand new family that does not contain any of the four current substrings (Haiku, Sonnet, Opus, default), the fallback at ShortcutSettings.swift line 188 labels it with the API name and stuffs it at order 99. That is intentional, but the picker layout was sized for three families. A fourth family ships fine functionally but the UI grows.

The honest claim is narrow: the specific Opus 4.7 rename on April 22, 2026 was absorbed by a 13-line family map and a recovery branch that already existed in the codebase. The unreleased patch closes the last gap. That is what this page is about.

Want to see the rename absorbed live?

Book 20 minutes. We will open ShortcutSettings.swift on a shared screen, run a session/new through the ACP bridge, and show the model ID rewrite in real time.

Book a call

Frequently asked questions

What is Anthropic's new Claude model in April 2026?

Claude Opus 4.7. It went generally available on April 22, 2026, priced at $5 per million input tokens and $25 per million output tokens. Inside the Agent Client Protocol SDK that Fazm uses to talk to Claude, Opus 4.7 does not arrive under an `opus` slug. It arrives under the model ID `default`, which is the detail this page is built around. The bridge logs that show this are at /Users/matthewdi/fazm/acp-bridge/src/index.ts line 1272: `Raw models from ACP SDK: ...`

Why does the model ID `default` matter for downstream apps?

Because `default` is a pseudo-model. The ACP SDK reports it alongside the real, version-suffixed Opus 4.7 ID, and the real ID is the one that should appear in a model picker. Fazm's bridge filters `default` out at acp-bridge/src/index.ts line 1274 with the comment `Filter out the 'default' pseudo-model — it's not a real selectable model`. At the same time, Fazm's Swift settings layer in ShortcutSettings.swift line 174 contains `// ACP SDK v0.29+ uses 'default' for Opus 4.7; migrate stored 'opus' to match.` Those two lines, in two processes that talk to each other, carry opposite assumptions. The mismatch is exactly what produced the unreleased changelog fix.

What broke in Fazm when Opus 4.7 went GA on April 22, 2026?

The Smart model preference. A user who had selected Smart (Opus) before the upgrade had `opus` saved in UserDefaults under `shortcut_selectedModel`. After the v2.4.1 update, the migration in ShortcutSettings.swift line 325 normalized that to `default`. But the ACP bridge filtered `default` out of the published model list at acp-bridge/src/index.ts line 1274. The result: the saved selection did not match any item in the picker, so the picker fell back to Sonnet (Fast), and the chat header started rendering `Smart` for Sonnet users when ACP reported a partial model list. The CHANGELOG.json `unreleased` entry reads `Fixed Smart (Opus) model preference not persisting after app update — now correctly maps stored 'opus' to the new ACP model ID`.

How did Fazm even get Opus 4.7 in the picker without an app release?

Two days earlier, on April 20, 2026, Fazm 2.4.0 shipped a feature that the changelog describes as `Available AI models now populate dynamically from the agent, so newly released Claude models appear without an app update`. The mechanism is at /Users/matthewdi/fazm/Desktop/Sources/Providers/ChatProvider.swift line 1016: `await acpBridge.setModelsAvailableHandler { models in ... ShortcutSettings.shared.updateModels(models) }`. ACP emits a `models_available` JSON-RPC message after each `session/new`. ShortcutSettings.swift line 180 takes that list, runs each model ID through a substring match against a three-row family map at line 159, and rebuilds the picker on the main actor. So Opus 4.7 did appear automatically. The picker just could not match the user's saved preference to it.

Where is the three-row family map that labels Opus, Sonnet, and Haiku in the picker?

ShortcutSettings.swift line 159: `private static let modelFamilyMap: [(substring: String, short: String, family: String, order: Int)] = [(`haiku`, `Scary`, `Haiku`, 0), (`sonnet`, `Fast`, `Sonnet`, 1), (`opus`, `Smart`, `Opus`, 2), (`default`, `Smart`, `Opus`, 2)]`. The `default` row was added in v2.4.0 as the forward-compatibility hook. When a model ID like `claude-opus-4-7-20260420` arrives, line 185 matches `opus` first and labels it `Smart (Opus, latest)`. When the SDK ships only `default`, the same row matches and the label is identical. The two-row treatment of Opus is intentional: the family map needs to label the model the same way regardless of which slug Anthropic emits.

What does the migration code in ShortcutSettings.swift line 325 actually do?

It runs once on launch, reads the saved `shortcut_selectedModel` from UserDefaults, and rewrites it through `Self.normalizeModelId(saved)`. The normalize function at line 170 returns `haiku` for any string containing `haiku`, `sonnet` for any string containing `sonnet`, and `default` for any string containing `opus`. If the rewrite changes the value, line 332 writes it back. This is why a user who upgraded from Fazm 2.3.x with `opus` saved would silently get `default` saved after first launch on 2.4.1, and then find their Smart preference unmatched against the filtered picker until the unreleased fix lands.

What is the actual fix for the preference-loss bug?

Read in two parts. First, ShortcutSettings.swift line 197-214 already had a recovery branch: if the saved selection is not in the new list, the code attempts to upgrade it by finding any model ID that contains the normalized substring (line 208: `if let upgraded = newModels.first(where: { $0.id.contains(normalizedSelection) })`). The problem is that for `default`, no real model ID contains the substring `default`. The fix in the unreleased changelog rewrites the normalize function so that stored `opus` no longer collapses to `default` at all. Instead, it returns the substring `opus`, and the recovery branch at line 208 finds the real `claude-opus-4-7` model ID. The two-row family map already supports this because `opus` and `default` both produce the `Smart (Opus, latest)` label.

Did Fazm 2.4.1 on April 22, 2026 fix the entire problem?

No. 2.4.1 specifically fixed the model label bug: `Fixed model label showing 'Smart' for Sonnet users when Anthropic reports a partial model list`. That fix went in at the label resolution path through `selectedModelShortLabel` (line 232) and the chained fallback in `shortLabel(for:)` at line 222, so a Sonnet-only model list no longer mislabels the picker. The deeper preference-persistence bug — saved `opus` does not match any item in the new list — required the unreleased entry to fully resolve. So the timeline is v2.4.0 ships dynamic model loading, v2.4.1 fixes the surface label, and the next patch fixes the persistence path.

How does Fazm pick a model in chat at all? Through Anthropic directly?

No. Through the Agent Client Protocol bridge. Every chat session is created through `acpBridge.warmupSession` in ChatProvider.swift line 1047, which sends a `session/new` JSON-RPC request to a Node.js process running the Anthropic-published `@anthropic/claude-agent-sdk-acp`. That process returns the available model list inside `result.models.availableModels`. The bridge filters out `default` and emits `models_available` to Swift over stdio. Swift updates the picker. When the user picks Smart, ChatProvider sends `session/set_model` with the matching model ID. Fazm never talks directly to api.anthropic.com from Swift; the ACP bridge is the only path.

Is there a one-screen reproduction I can verify on my own machine?

Yes. With Fazm running, set the saved preference manually with `defaults write com.fazm.app shortcut_selectedModel opus`, then quit and relaunch the app. Then open the floating bar and check the Smart/Fast pill in the chat header. On Fazm versions between 2.4.0 and 2.4.1 with no Anthropic call to refresh the model list yet, the pill renders Sonnet's label even though the saved preference was Opus. After the unreleased fix lands, the same `defaults write` followed by relaunch lands you on the real `claude-opus-4-7` ID with the Smart label intact.