Skip to content

Guard close-confirmation alert against re-entrant presentation - #667

Merged
onevcat merged 1 commit into
mainfrom
fix/close-confirmation-reentrancy
Aug 2, 2026
Merged

Guard close-confirmation alert against re-entrant presentation#667
onevcat merged 1 commit into
mainfrom
fix/close-confirmation-reentrancy

Conversation

@onevcat

@onevcat onevcat commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the SIGABRT crash in Sentry issue PROWL-MACOS-FP (SIGABRT: Close Terminal Tab? > terminating due to uncaught exception of type NSException).

Root cause: close requests reach presentCloseConfirmation from independent paths (TCA effects, Ghostty callbacks, the tab bar, the CLI socket). NSAlert.runModal() keeps draining main-actor jobs while it spins (the modal loop runs in common modes), so a late close request could present a second confirmation nested inside the first one's run loop. On macOS 27 beta, AppKit answers that nested modal with an NSException that is rethrown out of runModal into Swift frames, which cannot catch ObjC exceptions → std::terminate → SIGABRT.

The crash report's OS crash-info showed the on-screen alert was "Close Terminal Tab?" (.tab target) while the crashing stack was presenting the .pane alert from handleCloseRequest — two different alerts, confirming the nesting.

Fix

Add TerminalCloseConfirmationGate, an app-global @MainActor guard: while a close confirmation is on screen, later presentCloseConfirmation calls are dropped and treated as cancelled (return false). All close paths funnel through presentCloseConfirmation, so the gate covers TCA, Ghostty-callback, tab-bar, and CLI triggers alike.

Testing

  • New TerminalCloseConfirmationGateTests (3 tests, passing): free-gate execution, nested run dropped, gate released after run.
  • make check passes.
  • make build-app succeeds.

https://claude.ai/code/session_01TWSNesKV4dk8HLKmcNEJjS

Close requests reach presentCloseConfirmation from independent paths
(TCA effects, Ghostty callbacks, the tab bar, the CLI socket) while
NSAlert.runModal() keeps draining main-actor jobs, so a late request
could present a second alert nested inside the first one's run loop.
On macOS 27 beta AppKit answers that with an NSException rethrown out
of runModal, which Swift cannot catch (SIGABRT, Sentry PROWL-MACOS-FP).

Add TerminalCloseConfirmationGate: while a confirmation is on screen,
later ones are dropped and treated as cancelled.

Claude-Session: https://claude.ai/code/session_01TWSNesKV4dk8HLKmcNEJjS

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Ready to approve

The change is narrowly scoped, the re-entrancy guard is applied at the shared confirmation entry point, and tests cover the intended gating behavior.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR introduces an app-global main-actor “gate” to prevent re-entrant NSAlert.runModal() close-confirmation prompts from being presented while an existing confirmation is already on screen, addressing a SIGABRT crash caused by nested modal alerts.

Changes:

  • Added TerminalCloseConfirmationGate to drop nested close-confirmation presentations and treat them as cancelled.
  • Routed WorktreeTerminalState.presentCloseConfirmation through the gate and mapped “dropped” to false.
  • Added Swift Testing coverage for the gate’s basic behavior and re-entrancy protection.
File summaries
File Description
supacodeTests/TerminalCloseConfirmationGateTests.swift Adds unit tests validating gating behavior, nested-call dropping, and post-run release.
supacode/Features/Terminal/Models/WorktreeTerminalState+Surfaces.swift Wraps the NSAlert.runModal() confirmation prompt with the new gate and treats drops as cancelled.
supacode/Features/Terminal/Models/TerminalCloseConfirmationGate.swift Introduces the global @MainActor guard that prevents re-entrant close-confirmation presentation.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@@ -0,0 +1,24 @@
import Foundation
@onevcat
onevcat merged commit 0e2cb15 into main Aug 2, 2026
2 checks passed
@onevcat
onevcat deleted the fix/close-confirmation-reentrancy branch August 2, 2026 05:56
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.

2 participants