Skip to content

Revert liveSurfaceForGhosttyAccess: fix Cmd+N @Published nil crash - #2221

Merged
austinywang merged 1 commit into
mainfrom
issue-2212-cmd-n-published-null
Mar 26, 2026
Merged

austinywang merged 1 commit into
mainfrom
issue-2212-cmd-n-published-null

Conversation

@austinywang

@austinywang austinywang commented Mar 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Reverts PR Fix #1870: prevent split crash on Intel Macs caused by stale font pointer #1915 (liveSurfaceForGhosttyAccess / cmuxPointerAppearsLive guard) which introduced a regression causing Cmd+N to crash with a nil @Published value
  • Restores direct .surface access for config inheritance in TabManager and Workspace, removing the quarantine/liveness-check layer that was rejecting valid surfaces during new-tab creation
  • Removes the cmuxPointerAppearsLive pointer guard from cmuxCurrentSurfaceFontSizePoints and simplifies the font fallback logic

Fixes #2212

Test plan

  • Cmd+N to create new tabs repeatedly — no crash
  • Split panes inherit font size correctly
  • Verify on Intel Mac if possible (original cmux split screen will crash #1870 fix target)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor
    • Optimized terminal configuration inheritance by streamlining internal surface access patterns and simplifying font fallback logic.

Summary by cubic

Fixes a crash when pressing Cmd+N caused by a nil @published value during new-tab creation. Reverts the liveSurfaceForGhosttyAccess quarantine and restores direct .surface access for config inheritance. Fixes #2212.

Written for commit a47c8af. Summary will update on new commits.

…ntel-1870"

This reverts commit c5b3066, reversing
changes made to 100612d.
@vercel

vercel Bot commented Mar 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Building Building Preview, Comment Mar 26, 2026 11:00pm

@coderabbitai

coderabbitai Bot commented Mar 26, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7df018b0-4ed7-42de-b0c8-fe6389631c00

📥 Commits

Reviewing files that changed from the base of the PR and between bc9e45c and a47c8af.

📒 Files selected for processing (2)
  • Sources/TabManager.swift
  • Sources/Workspace.swift

📝 Walkthrough

Walkthrough

The PR refactors how TabManager and Workspace retrieve terminal surfaces for config inheritance. It replaces guarded liveSurfaceForGhosttyAccess(reason:) calls with direct surface.surface pointer access, removes redundant liveness checks, and simplifies font-fallback tracking logic in Workspace.

Changes

Cohort / File(s) Summary
Surface Access Pattern
Sources/TabManager.swift, Sources/Workspace.swift
Replaced panel.surface.liveSurfaceForGhosttyAccess(reason:) with direct panel.surface.surface access for terminal config inheritance source selection. Removed "best-effort" liveness check for quicklookFont in cmuxCurrentSurfaceFontSizePoints.
Fallback Logic Simplification
Sources/Workspace.swift
Eliminated staleRootedFontFallback tracking and conditional debug labeling ("quarantinedRootedFont" vs "lastKnownFont"); fallback now solely uses lastTerminalConfigInheritanceFontPoints with debug output always reporting lastKnownFont.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #2173: Modifies TabManager/terminal-config and surface-liveness handling for config inheritance selection
  • PR #2176: Updates inherited terminal configuration/font-size logic for new workspaces, involving snapshot and inheritance methods
  • PR #2101: Changes the same surface validation code paths for terminal config inheritance

Poem

A rabbit hops through surface webs,
No liveness checks slow its steps—
Direct paths now, swift and clean,
Fallback fonts grow lean,
Terminal configs bloom anew! 🐰✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2212-cmd-n-published-null

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@austinywang
austinywang merged commit 32124d9 into main Mar 26, 2026
12 of 14 checks passed
@greptile-apps

greptile-apps Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR reverts #1915 to fix a Cmd+N crash where liveSurfaceForGhosttyAccess was actively side-effecting (setting self.surface = nil on a @Published property) during config-inheritance calls triggered by new-tab creation, which broke the normal surface lifecycle. The change is correct in removing the quarantine side-effect from the read-only inheritance path, but the implementation goes further than strictly necessary, introducing two lower-level risks:\n\n- Font-pointer liveness guard removed — cmuxPointerAppearsLive(quicklookFont) in cmuxCurrentSurfaceFontSizePoints was the targeted fix for Intel Mac CTFont crashes (#1496, #1870). Removing it is not required to fix #2212, and could regress those crashes on Intel hardware.\n- hasLiveSurface not checked in Workspace.inheritedTerminalConfig and rememberTerminalConfigInheritanceSource — the new guard is terminalPanel.surface.surface != nil, which does not check portalLifecycleState. A surface mid-teardown can pass this guard and its pointer is then passed to ghostty_surface_inherited_config / ghostty_surface_quicklook_font, which the codebase documentation (lines 2844–2851 of GhosttyTerminalView.swift) explicitly flags as unsafe without the liveness gate. The read-only hasLiveSurface property (surface != nil && portalLifecycleState == .live) would close this gap without re-introducing the @Published-mutation crash.

Confidence Score: 3/5

Safe to merge on the happy path, but two deliberate guard removals risk re-introducing separate crashes: one on Intel Macs (font pointer) and one with teardown-phase surfaces (portalLifecycleState).

The primary crash (#2212, Cmd+N nil @published) is correctly fixed by removing the side-effecting quarantine logic from config-inheritance paths. The TabManager change is clean. However, the Workspace loop and rememberTerminalConfigInheritanceSource now use only pointer-nullity guards — the documented safe access pattern requires checking portalLifecycleState too. The font-pointer liveness guard removal is also an unforced regression of a previous Intel Mac fix not required to close #2212.

Sources/Workspace.swift — font pointer guard at line 48 and loop guard at lines 7318/7220 need targeted fixes before merge.

Important Files Changed

Filename Overview
Sources/Workspace.swift Removes the font-pointer liveness guard from cmuxCurrentSurfaceFontSizePoints (potential Intel Mac crash regression), simplifies inheritedTerminalConfig to drop staleRootedFontFallback, and replaces liveSurfaceForGhosttyAccess with direct .surface access — but the new guard only checks pointer nullity, not portalLifecycleState, leaving surfaces in teardown eligible to be used.
Sources/TabManager.swift Reverts liveSurfaceForGhosttyAccess to direct .surface.surface access in inheritedTerminalConfigForNewWorkspace; the upstream candidate-selection function already applies a hasLiveSurface filter before returning the panel, so the risk here is lower than in Workspace.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Cmd+N / New Split triggered"] --> B["inheritedTerminalConfigForNewWorkspace\n(TabManager)"]
    A --> C["inheritedTerminalConfig\n(Workspace)"]

    B --> D["terminalPanelForWorkspaceConfigInheritanceSource\n(already filters hasLiveSurface)"]
    D --> E{"panel.surface.surface != nil?"}
    E -- yes --> F["cmuxInheritedSurfaceConfig(sourceSurface)"]
    E -- no --> G["fallback: lastRememberedTerminalFontPoints"]
    F --> H["ghostty_surface_inherited_config ✅"]
    F --> I["cmuxCurrentSurfaceFontSizePoints"]
    I --> J{"cmuxSurfacePointerAppearsLive? ✅"}
    J -- yes --> K["ghostty_surface_quicklook_font"]
    K --> L{"cmuxPointerAppearsLive(font)?\n❌ REMOVED — Intel Mac risk"}
    L -- was guarded --> M["CTFont.fromOpaque → CTFontGetSize"]

    C --> N["terminalPanelConfigInheritanceCandidates\n(all panels, no lifecycle filter)"]
    N --> O{"surface.surface != nil?\n⚠️ missing hasLiveSurface check"}
    O -- passes --> F
    O -- all fail --> P["fallback: lastTerminalConfigInheritanceFontPoints"]
Loading

Reviews (1): Last reviewed commit: "Revert "Merge pull request #1915 from el..." | Re-trigger Greptile

Comment thread Sources/Workspace.swift
return nil
}

let ctFont = Unmanaged<CTFont>.fromOpaque(quicklookFont).takeUnretainedValue()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Font pointer guard removal may re-introduce Intel Mac crash

The guard cmuxPointerAppearsLive(quicklookFont) was explicitly added to fix crashes on Intel Macs where ghostty_surface_quicklook_font returned a freed CTFont pointer even while the surface itself was still live (issues #1496, #1870). The comment that was removed stated:

"This does not prove the object is still a valid CTFont, but it filters out the common fully-freed/unmapped cases that previously crashed on Intel Macs"

The surface-level cmuxSurfacePointerAppearsLive(surface) check at the top of the function remains, but that only validates the ghostty_surface_t pointer — it does not guarantee that the CTFont * returned by ghostty_surface_quicklook_font is still backed by a live allocation. If font objects can be freed or reallocated independently of their parent surface (e.g., during a font-size change mid-flight), calling Unmanaged<CTFont>.fromOpaque(quicklookFont).takeUnretainedValue() on a freed pointer will still crash.

The PR description acknowledges this risk with "Verify on Intel Mac if possible (original #1870 fix target)", but the guard removal is a deliberate regression of a targeted crash fix. Consider keeping the font pointer check, or confirming via testing on Intel hardware that ghostty_surface_quicklook_font can never return a freed pointer while the surface passes cmuxSurfacePointerAppearsLive.

Comment thread Sources/Workspace.swift
}
continue
}
guard let sourceSurface = terminalPanel.surface.surface else { continue }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 surface.surface != nil skips the portalLifecycleState teardown check

hasLiveSurface is defined as surface != nil && portalLifecycleState == .live. The new guard only tests the pointer nullity half of that invariant; it will pass for surfaces that are mid-teardown (portalLifecycleState != .live) but still have a non-nil pointer.

The documentation in GhosttyTerminalView.swift (lines 2844–2851) explicitly warns that a surface should be validated before passing it to Ghostty C APIs like ghostty_surface_inherited_config and ghostty_surface_quicklook_font, because "a Swift wrapper around ghostty_surface_t can remain non-nil after the backing native surface has already been freed."

The original crash from #2212 was caused by liveSurfaceForGhosttyAccess mutating self.surface = nil (a @Published property) as a side-effect during config inheritance. Simply using hasLiveSurface — which is a pure read with no side-effects — would be a safer middle ground:

Suggested change
guard let sourceSurface = terminalPanel.surface.surface else { continue }
guard terminalPanel.surface.hasLiveSurface,
let sourceSurface = terminalPanel.surface.surface else { continue }

This avoids the @Published mutation that caused #2212 while also not admitting surfaces that are in the process of being torn down.

Comment thread Sources/Workspace.swift
if let sourceSurface = terminalPanel.surface.liveSurfaceForGhosttyAccess(
reason: "workspace.rememberConfigInheritanceSource"
),
if let sourceSurface = terminalPanel.surface.surface,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same hasLiveSurface gap in rememberTerminalConfigInheritanceSource

Only pointer nullity is checked here, not portalLifecycleState. A surface mid-teardown with a non-nil pointer will be used to call cmuxCurrentSurfaceFontSizePoints, updating terminalInheritanceFontPointsByPanelId and lastTerminalConfigInheritanceFontPoints with whatever the (potentially stale) surface reports.

For consistency with the inheritedTerminalConfig loop, consider:

Suggested change
if let sourceSurface = terminalPanel.surface.surface,
if terminalPanel.surface.hasLiveSurface,
let sourceSurface = terminalPanel.surface.surface,

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…lit-crash-intel-1870" (manaflow-ai#2221)

This reverts commit df80486, reversing
changes made to 37c7ccd.

This branch was successfully deployed

1 active deployment
Preview — a47c8af8 Deployed Mar 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cmd+N crash: null @Published property in TabManager.addWorkspace (3rd crash site)

1 participant