9.8 KiB
What's in 2.5.1
A patch release that bundles every issue reported against 2.5.0 plus a couple of TestFlight-driven iOS fixes. No data migrations needed — drop-in replacement for 2.5.0 on Mac, drop-in TestFlight build on iOS.
Bug fixes
Mac
-
#49 — macOS 26 "Scarf.app is damaged" recovery path. Verified the shipped 2.5.0 bundles pass
codesign --verify --strict --deepandspctl --assesson macOS 26.4.1; the user-facing "damaged" symptom in some reports turned out to be self-inflicted by destructive recovery commands. Added a Troubleshooting section to the README documenting the non-destructive fix path (xattr -d com.apple.quarantineonly — neverxattr -rcorcodesign --force --deep --sign -). Hardened the release pipeline: every variant zip now goes throughcodesign --verify --strict --deep+spctl --assessafter the finalditto, so any future regression in the shipped artifact fails the release before a user sees it. -
#46 — chat performance: long sessions no longer bog down or crash. Long chats were doing O(n) work per streamed token because every chunk rebuilt the full message-group array AND every
MessageGroupView/RichMessageBubblere-evaluated its body. Three changes cap per-chunk work at O(1) for settled groups:MessageGroupViewandRichMessageBubbleare nowEquatablewith.equatable()short-circuit. Settled bubbles skip body re-eval entirely while the streaming bubble still redraws.RichChatViewModel.upsertStreamingMessagepatches the trailing group in place via a newpatchTrailingGroupForStreaming(...)instead of runningbuildMessageGroups()per chunk.MessageGroup.toolKindCountsmoved to the model (was anO(m × k)computed property re-running on every render).ToolCallCard.formatJSONcached via.task(id: callId).ToolResultContent.linescached on content change.
CPU during streaming on a 500-message session drops from sustained 100%+ to ~30–50% on representative hardware.
-
#50 — Hermes v0.11 profile awareness. Hermes v0.11 stores each profile in its own
~/.hermes/profiles/<name>/directory with its ownstate.db,sessions/,config.yaml,memories/, etc. Pre-fix Scarf hardcoded~/.hermesand ignored~/.hermes/active_profile, sohermes profile use coderfollowed by a Scarf relaunch silently read the wrong DB — sessions, memory, cron all coming from the default profile. NewHermesProfileResolverreadsactive_profileand resolves the effective home path;HermesPathSet.defaultLocalHomeconsults it, so every derived path automatically follows the active profile. SessionInfoBar gains a profile chip when not on the default so users can see which profile Scarf is reading from. -
#53 — granular reasons on the "Connected — can't read Hermes state" pill. Tier 2 of the connection probe now distinguishes config.yaml-missing /
~/.hermes-missing / permission-denied / Hermes-profile-active and surfaces a pill popover with the specific reason + an actionable hint + Run Diagnostics / Retry buttons. Profile case includes a copy-pastehermes profile use defaultaffordance. -
#44 — pill and Run Diagnostics no longer disagree. A long-standing latent bug surfaced by Tailscale Mac-to-Mac users: the pill probe and the diagnostics view ran the same
[ -r ~/.hermes/config.yaml ]check but went through different transport paths —transport.runProcessfor the pill (whichremotePathArg-quotes every argument and mangled the multi-line script) vs raw/usr/bin/ssh ... -- /bin/sh -sfor diagnostics. Result: 14/14 diagnostics passing while the pill stayed stuck on "can't read Hermes state". Extracted the diagnostics workaround into a sharedSSHScriptRunnerin ScarfCore; both probes now use it. Side benefit: the granular #53 probe script (more$VARs and nested quotes) is robust against the same class of bug going forward. -
#54 — Add Project on remote server contexts. The Add Project sheet always rendered a Browse button backed by
NSOpenPanel(a Mac-local file dialog). On a remote SSH context the user picked a Mac path, the path landed in the projects registry as the project's "remote" working directory, and tool calls failed at runtime because that path doesn't exist on the Linux server. Tier-1 fix: sheet is now context-aware — local context keeps Browse unchanged; remote context hides Browse, shows a"Path on <server> — must already exist on the server"hint, and adds a Verify button that runstransport.stat(path)and renders inline ✓ / ⚠. A full SFTP-backed remote picker remains a deferred feature.
ScarfGo (iOS)
- #46 — same O(n)-per-token fix on iOS. ScarfGo uses a different chat path (
LazyVStackdirectly overcontroller.vm.messages, not message groups) so the Mac fix'sEquatableconformances didn't propagate. Added an iOS-equivalentMessageBubble: Equatablewith.equatable()at theForEachcall site — settled bubbles short-circuit body re-eval while the streaming bubble still redraws. - #51 — keyboard now dismissable. Pre-fix the chat composer's
TextFieldhad no@FocusState, no.scrollDismissesKeyboard, and no keyboard accessory toolbar; withaxis: .vertical+.submitLabel(.send)the Return key inserts a newline rather than submitting. Once the keyboard rose it stuck — hiding the system tab bar (which iOS auto-hides while a keyboard is up) and trapping users in the Chat tab. Added two redundant dismissal paths:.scrollDismissesKeyboard(.interactively)on the message list (drag messages downward to collapse) AND akeyboard.chevron.compact.downbutton in the keyboard accessory toolbar. Tab bar reappears on dismiss → users can switch tabs again. - #55 — first-run Cancel button no longer looks broken. TestFlight feedback: the "Connect to Hermes" onboarding's Cancel button appeared dead. Root cause:
RootModel.cancelOnboardinghad a defensiveservers.isEmptybranch that re-presented a fresh onboarding view when there was nothing to fall back to, making the button fire correctly but visually do nothing. The fix is at the right layer:OnboardingRootViewnow takes acanCancel: Boolparameter and hides the Cancel button entirely when there's no server list to return to.
New features (Mac)
-
Chat density preferences (#47 + #48). New section in Settings → Display → Chat density. All defaults match today's UI exactly so existing users see no change until they opt in.
- Tool calls: Full card (default) / Compact chip / Hidden. Compact renders each call as a single-line tappable chip — kind icon + function name + status dot — that opens the right-pane inspector with the same details the inline expand shows. Hidden skips per-call rows; the always-visible group summary pill ("Used 5 tools (3 read, 2 edit)") becomes tappable so the inspector pane is still one click away.
- Reasoning: Disclosure box (default) / Inline (italic) / Hidden. Inline collapses the yellow disclosure to italic faded caption text inline above the reply with a small brain prefix — same data, far less vertical space. Hidden skips reasoning entirely.
- Chat font size: 85% to 130% slider (5% step). Applied at the chat root via
.environment(\.dynamicTypeSize, ...)so message list, input bar, session info bar, and inspector pane all scale together.
All density toggles preserve existing telemetry surfaces — per-turn stopwatch, per-message tokens, finish reason, and timestamp stay in the bubble metadata footer; SessionInfoBar input/output/reasoning tokens, USD cost, model, project, git branch, and started-at relative time are unaffected by every density setting.
New features (ScarfGo iOS)
- iCloud Keychain sync for SSH keys (#52). Reddit-reported friction: every iOS device needed its own SSH key. Pairing iPhone + iPad meant onboarding twice and editing
authorized_keysper device. New opt-in toggle in System → Security: when enabled, the SSH key bundle is stored withkSecAttrAccessibleAfterFirstUnlock+kSecAttrSynchronizable=trueso iCloud Keychain picks it up on every signed-in device. Default off (preserves today's behavior on update). Toggling triggers a one-shot migration that re-saves all stored keys with the target attributes; failure reverts the toggle and surfaces the error inline. With Advanced Data Protection enabled, the encryption keys never leave your devices.
Documentation + tooling
- Privacy / sandboxing claim corrected. Previous CLAUDE.md / README implied Scarf ran sandboxed; it doesn't (and can't, given that it spawns the user-installed
hermesbinary and reads~/.hermes/directly). Documentation now reflects the actual posture. - Release pipeline hardened.
scripts/release.shnow extracts each variant's distribution zip and runscodesign --verify --strict --deep+spctl --assess --type executeon the extracted bundle as a final gate. Catches any future regression in the shipped artifact pre-ship rather than via user reports.
Notes for users running 2.5.0
No data migrations needed. Server configs, Keychain entries, project registries, session attribution sidecar — all forward-compatible. The iCloud Keychain sync toggle defaults to off, so existing iOS users keep their device-local keys until they opt in.