Skip to content

Cmd+N crash: swift_retain PAC failure in addWorkspace (Release-only, post #2178) #2180

@austinywang

Description

@austinywang

Crash

Pressing Cmd+N crashes with EXC_BAD_ACCESS (SIGSEGV) — pointer authentication failure in swift_retain called from TabManager.addWorkspace. This is a use-after-free of a Swift object that only manifests in Release (nightly) builds, not Debug (dev) builds.

This crash persists after both PR #2173 and PR #2178. The nightly build 2357244224201 was built from commit b93be12 (the #2178 merge commit itself).

Related issues: #2157, #2169
Related PRs: #2173, #2178

Environment

  • cmux: 0.62.2-nightly.2357244224201 (b93be12)
  • macOS: 26.3 (25D125)
  • Chip: Apple Silicon (Mac15,10)
  • Crashed ~3 seconds after launch

Stack trace

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libswiftCore.dylib    swift_retain + 24
1   cmux                  TabManager.addWorkspace(...) + 760
2   cmux                  AppDelegate.addWorkspaceInPreferredMainWindow(...) + 416
3   cmux                  AppDelegate.handleCustomShortcut(event:) + 6496
4   cmux                  closure #1 in AppDelegate.installShortcutMonitor() + 152

Exception

Exception Type:    EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0a36d1a102f4de88 -> 0x000051a102f4de88
                   (possible pointer authentication failure)

The PAC-stripped address 0x000051a102f4de88 is "not in any region" — the object has been completely freed and its memory unmapped.

Why this is a new variant

Issue Crash site Object type PR fix
#2169 workspaceCreationSnapshot() + 1260 null pointer in memmove #2173
#2157 workspaceCreationSnapshot() / newTabInsertIndex dangling ghostty_surface_config_s pointers #2178
This addWorkspace() + 760 Swift object (swift_retain PAC failure)

PR #2178 sanitized the C struct (ghostty_surface_config_s) to avoid dangling Ghostty pointers. But this crash is in swift_retain — it's retaining a Swift object that was already freed. The offset +760 places this further into addWorkspace, past snapshot creation, likely in the tab array manipulation or workspace wiring phase.

Why Release-only

ARC optimizations in Release builds (-O/-Osize) eliminate intermediate retain/release pairs. An object that stays alive in Debug (due to extra retains) gets freed earlier in Release. The background git metadata probe on Thread 2 (com.cmux.initial-workspace-git-probe) running initialWorkspaceGitMetadataSnapshot concurrently may contribute a race condition:

Thread 2::  Dispatch queue: com.cmux.initial-workspace-git-probe
...
3   cmux    specialized static TabManager.runCommandResult(...)
4   cmux    static TabManager.workspacePullRequestSnapshot(...)
5   cmux    static TabManager.initialWorkspaceGitMetadataSnapshot(for:)
6   cmux    closure #1 in TabManager.scheduleWorkspaceGitMetadataRefresh(...)

Likely candidates at addWorkspace + 760

Looking at TabManager.addWorkspace() (line 1201), offset +760 is past snapshot creation and likely in:

  1. var updatedTabs = tabs (line 1243) — retaining the array copies retains each Workspace element. If a workspace was freed concurrently, the retain of that stale reference crashes.
  2. tabs = updatedTabs (line 1249) — assigning back to @Published triggers Combine machinery that retains observers/subscribers.
  3. makeWorkspaceForCreation(...) (line 1228) — creating the workspace involves wiring up configTemplate: inheritedConfig which, despite Fix Cmd+N crash from workspace creation config snapshots #2178's sanitization, might still reference state from a deallocated surface.

Suggested fix

The root cause across all three variants is that addWorkspace accesses shared mutable state (tabs, selectedWorkspace, terminal panel surfaces) that can be invalidated by concurrent operations during the brief window between snapshot capture and workspace insertion. Consider:

  1. Audit all Swift object references in WorkspaceCreationSnapshot and the addWorkspace body for lifetime hazards under ARC optimization — explicit withExtendedLifetime on any workspace/panel accessed during creation.
  2. Gate the background git probe to not run during workspace creation, or ensure it never touches workspace-level state on the main actor.
  3. Reproduce in Release by building with swift build -c release or xcodebuild -configuration Release and rapidly pressing Cmd+N on launch.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions