Skip to content

Add warnBeforeClosingTab close-warning toggle - #2808

Merged
austinywang merged 2 commits into
mainfrom
issue-946-warn-before-closing-tab
May 11, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-946-warn-before-closing-tab

Conversation

@austinywang

@austinywang austinywang commented Apr 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #946.

Summary

  • add app.warnBeforeClosingTab to cmux.json loading, generated schema data, Settings search, and Settings UI
  • route tab close confirmation through one close-tab policy so Cmd+W, tab-strip, pane, runtime surface, and close-other-tabs paths inherit the same preference
  • add behavior-level Swift unit coverage for false, true, and default/unset config values

Validation

  • jq empty Resources/Localizable.xcstrings
  • jq empty web/data/cmux.schema.json
  • git diff --check
  • local tests/build intentionally not run per repo/task instruction; CI owns XCTest and build validation

Note

Medium Risk
Changes tab/workspace close confirmation behavior across multiple close paths (Cmd+W, tab-strip gestures, batch closes, and runtime surface closes), so regressions could lead to unintended silent closes or extra prompts. Scope is limited to close-confirmation gating and adds unit tests plus schema/UI wiring.

Overview
Adds a new app.warnBeforeClosingTab preference (default enabled) that can be set via cmux.json, surfaced in Settings UI, indexed in Settings search, and documented in the JSON schema and localizations.

Routes tab/workspace close confirmations through a shared CloseTabConfirmationPolicy, so Cmd+W/tab-strip closes, close-other-tabs, pane closes, runtime surface closes, and pinned-workspace last-surface behavior consistently honor the new toggle.

Extends XCTest coverage to verify enabled/disabled/default behavior, including config-file parsing and pinned-workspace edge cases.

Reviewed by Cursor Bugbot for commit 02313ed. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Apr 11, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 11, 2026 1:30am

@coderabbitai

coderabbitai Bot commented Apr 11, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a UserDefaults-backed setting that reuses the quit-warning key to control whether tab/workspace/window close confirmations are shown, threads a closeTrigger through workspace-close APIs to conditionally skip prompts, updates localization and schema text, expands tests, and adds build helper scripts.

Changes

Cohort / File(s) Summary
Localization
Resources/Localizable.xcstrings
Updated two English strings and one Japanese translation/state for quit/confirmation wording; no new keys added.
Settings schema & parsing
web/data/cmux-settings.schema.json, Sources/KeyboardShortcutSettingsFileStore.swift
Adjusted app.warnBeforeQuit schema description; parsing now recognizes legacy app.warnBeforeClosingTab boolean and maps it to the managed quit-warning key.
Settings UI & storage
Sources/cmuxApp.swift
Added CloseTabWarningSettings helpers that reuse the quit-warning UserDefaults key and updated Settings UI subtitle text.
Close-confirmation control flow
Sources/TabManager.swift, Sources/Workspace.swift, Sources/AppDelegate.swift
Added WorkspaceCloseTrigger and propagated a closeTrigger through close-confirmation APIs; confirmation dialogs are short-circuited when the CloseTab warning is disabled.
Tests
cmuxTests/...
cmuxTests/TabManagerUnitTests.swift, cmuxTests/AppDelegateShortcutRoutingTests.swift, cmuxTests/WorkspaceUnitTests.swift
Added/updated tests to isolate and restore the quit-warning preference, verify prompts are skipped or shown per setting, and cover multi-workspace and last-surface scenarios.
Build helper scripts
scripts/cmux-clang-wrapper.sh, scripts/install-clang-wrapper.sh, scripts/reload.sh
Added a clang-wrapper shim and installer; reload.sh can install/activate the wrapper toolchain and export TOOLCHAINS for subsequent xcodebuild invocations.

Sequence Diagram

sequenceDiagram
    actor User
    participant SettingsUI as "Settings UI"
    participant UserDefaults as "UserDefaults"
    participant TabManager as "TabManager"
    participant Workspace as "Workspace"
    participant Dialog as "Close Dialog"

    User->>SettingsUI: Toggle "Warn Before Closing Tab"
    SettingsUI->>UserDefaults: Save setting state

    User->>TabManager: Request close tab/workspace/window (with closeTrigger)
    TabManager->>UserDefaults: Check CloseTabWarningSettings.isEnabled()
    alt enabled
        UserDefaults-->>TabManager: true
        TabManager->>Workspace: request confirmClosePanel(closeTrigger)
        Workspace->>Dialog: Present confirmation dialog
        Dialog-->>User: "Confirm close?"
        User-->>Dialog: Confirm/Cancel
        Dialog-->>Workspace: response
        Workspace-->>TabManager: proceed or cancel
    else disabled
        UserDefaults-->>TabManager: false
        TabManager->>Workspace: Skip confirmation, close immediately
        Workspace-->>TabManager: closed
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I nibble prompts beneath the moonlight,
A toggle flips — close swift or hold tight.
Tabs hop away or wait for a nod,
Little paws cheer the settings applaud.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes significant out-of-scope additions: lifecycle-driven terminal surface refresh scheduling, clang wrapper toolchain scripts, and AppDelegate lifecycle observers unrelated to the #946 tab-close confirmation feature. Separate the clang wrapper and lifecycle refresh features into dedicated PRs, keeping this PR focused solely on the tab close confirmation toggle feature from #946.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers the main changes and validation steps, but lacks detail on testing coverage and manual verification as required by the template. Add a Testing section confirming what was tested (even if CI owns validation), describe manual verification steps, and address why local tests were intentionally not run per repo policy.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add warnBeforeClosingTab close-warning toggle' clearly and concisely describes the main feature addition in this PR.
Linked Issues check ✅ Passed The PR successfully implements the core objective from #946: adding a configurable setting to disable tab close confirmation dialogs, reducing friction for frequent tab-close workflows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-946-warn-before-closing-tab

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.

@greptile-apps

greptile-apps Bot commented Apr 11, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an app.warnBeforeClosingTab toggle (default on) that suppresses the close-confirmation dialog across Cmd+W, tab-strip, pane, runtime-surface, and close-other-tabs paths, leaving warnBeforeQuit unchanged. All close-tab flows are routed through a shared CloseTabConfirmationPolicy, with a new closeWorkspaceFromCloseTabGesture entry point separating tab-gesture closes from direct workspace closes.

  • CloseTabWarningSettings / CloseTabConfirmationPolicy in cmuxApp.swift provide the single-source-of-truth read path; KeyboardShortcutSettingsFileStore wires cmux.json parsing and the schema template; SettingsView exposes the toggle with @AppStorage.
  • TabManager gains a CloseConfirmationSource enum and shouldConfirmClose helper to distinguish tab-gesture closes (.tabClose, checked against the policy) from direct workspace closes (.workspace, unchanged); closeWorkspacesWithConfirmation is now correctly gated for the multi-workspace-selection path.
  • New XCTest coverage validates false, true, and unset cmux.json values for single-panel, multi-workspace, and pinned-workspace-last-surface scenarios.

Confidence Score: 5/5

Safe to merge; all previously identified gaps in the close-confirmation routing are addressed in this revision.

The CloseTabConfirmationPolicy correctly gates every close-tab path described in the PR — tab-strip gesture, pane close, runtime surface, close-other-tabs, and multi-workspace batch close. The closeWorkspacesWithConfirmation gap flagged in earlier review rounds is now covered by shouldConfirmClose(source: .tabClose). The pinned-workspace last-surface path routes through the new closeWorkspaceFromCloseTabGesture, which consults the policy for both the pinned-workspace confirmation and the running-process check. The single-workspace Cmd+Shift+W path intentionally uses .workspace source and is not claimed by the PR to be in scope. Minor findings are style-level only.

No files require special attention.

Important Files Changed

Filename Overview
Sources/cmuxApp.swift Adds CloseTabWarningSettings and CloseTabConfirmationPolicy enums; adds Settings UI toggle; CloseTabWarningSettings.setEnabled is defined but never called (dead code).
Sources/TabManager.swift Introduces CloseConfirmationSource enum and shouldConfirmClose helper; adds closeWorkspaceFromCloseTabGesture for tab-strip path; closeWorkspacesWithConfirmation now correctly gates multi-workspace confirmation through CloseTabConfirmationPolicy via .tabClose source; all previously-flagged close paths are covered.
Sources/Workspace.swift Routes last-surface workspace close via tab gesture through closeWorkspaceFromCloseTabGesture; wraps both panelNeedsConfirmClose checks in CloseTabConfirmationPolicy.shouldConfirm to honour the toggle in all delegate paths.
Sources/WorkspaceCloseTabsBatching.swift Wraps confirmation gate with CloseTabConfirmationPolicy.shouldConfirm; force-close flag correctly mirrors needsConfirmation so panels with running processes are still force-closed when the toggle is off.
cmuxTests/TabManagerUnitTests.swift Good test coverage for disabled/enabled/unset config values; settingsFileBackupsDefaultsKey is hardcoded as a string literal rather than referencing a constant from production code.

Reviews (4): Last reviewed commit: "Honor cmux close-tab warning preference" | Re-trigger Greptile

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cmuxTests/TabManagerUnitTests.swift (1)

892-893: Use the production key constant instead of duplicating the UserDefaults key string.

This test hardcodes "warnBeforeClosingTabShortcut" even though CloseTabWarningSettings.warnBeforeClosingTabKey already exists. Reusing the constant avoids drift.

Proposed diff
-    private let warnBeforeClosingTabDefaultsKey = "warnBeforeClosingTabShortcut"
+    private let warnBeforeClosingTabDefaultsKey = CloseTabWarningSettings.warnBeforeClosingTabKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 892 - 893, Replace the
hardcoded UserDefaults key string used in TabManagerUnitTests by referencing the
production constant: change the private let warnBeforeClosingTabDefaultsKey to
use CloseTabWarningSettings.warnBeforeClosingTabKey so the test uses the same
source of truth; ensure any code that reads/writes
originalWarnBeforeClosingTabPreference still uses that variable name and
behavior but initialized from CloseTabWarningSettings.warnBeforeClosingTabKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 910-916: Change makeRuntimeCloseWarningTestSubject to avoid
fatalError: make it return an optional tuple (manager: TabManager, workspace:
Workspace, panelId: UUID)?; replace the guard fatalError branch with
XCTFail("Expected selected workspace and focused terminal panel") and return
nil, using the existing properties selectedWorkspace, focusedPanelId, and
terminalPanel(for:) to populate the tuple on success; update callers in tests to
either XCTUnwrap the result or handle the optional so failures are reported
per-test instead of aborting the process.

---

Nitpick comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 892-893: Replace the hardcoded UserDefaults key string used in
TabManagerUnitTests by referencing the production constant: change the private
let warnBeforeClosingTabDefaultsKey to use
CloseTabWarningSettings.warnBeforeClosingTabKey so the test uses the same source
of truth; ensure any code that reads/writes
originalWarnBeforeClosingTabPreference still uses that variable name and
behavior but initialized from CloseTabWarningSettings.warnBeforeClosingTabKey.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3ef3acf-024b-4945-a624-4368d60ed961

📥 Commits

Reviewing files that changed from the base of the PR and between d289f66 and 061f3a9.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • cmuxTests/TabManagerUnitTests.swift
  • web/data/cmux-settings.schema.json

Comment thread cmuxTests/TabManagerUnitTests.swift Outdated
Comment on lines +910 to +916
private func makeRuntimeCloseWarningTestSubject() -> (manager: TabManager, workspace: Workspace, panelId: UUID) {
let manager = TabManager()
guard let workspace = manager.selectedWorkspace,
let panelId = workspace.focusedPanelId,
let terminalPanel = workspace.terminalPanel(for: panelId) else {
fatalError("Expected selected workspace and focused terminal panel")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid fatalError in test fixture setup helpers.

If fixture creation fails, fatalError aborts the test process instead of failing just the current test. Prefer returning nil with XCTFail so failures stay isolated.

Proposed diff
-    private func makeRuntimeCloseWarningTestSubject() -> (manager: TabManager, workspace: Workspace, panelId: UUID) {
+    private func makeRuntimeCloseWarningTestSubject(
+        file: StaticString = `#filePath`,
+        line: UInt = `#line`
+    ) -> (manager: TabManager, workspace: Workspace, panelId: UUID)? {
         let manager = TabManager()
         guard let workspace = manager.selectedWorkspace,
               let panelId = workspace.focusedPanelId,
               let terminalPanel = workspace.terminalPanel(for: panelId) else {
-            fatalError("Expected selected workspace and focused terminal panel")
+            XCTFail("Expected selected workspace and focused terminal panel", file: file, line: line)
+            return nil
         }

         terminalPanel.surface.setNeedsConfirmCloseOverrideForTesting(true)
         workspace.updatePanelShellActivityState(panelId: panelId, state: .unknown)
-        return (manager, workspace, panelId)
+        return (manager, workspace, panelId)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 910 - 916, Change
makeRuntimeCloseWarningTestSubject to avoid fatalError: make it return an
optional tuple (manager: TabManager, workspace: Workspace, panelId: UUID)?;
replace the guard fatalError branch with XCTFail("Expected selected workspace
and focused terminal panel") and return nil, using the existing properties
selectedWorkspace, focusedPanelId, and terminalPanel(for:) to populate the tuple
on success; update callers in tests to either XCTUnwrap the result or handle the
optional so failures are reported per-test instead of aborting the process.

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread cmuxTests/TabManagerUnitTests.swift

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/TabManagerUnitTests.swift">

<violation number="1" location="cmuxTests/TabManagerUnitTests.swift:915">
P3: `fatalError` in a test fixture helper aborts the entire test process instead of failing just the current test. Use `XCTFail` and return an optional so failures remain isolated.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread cmuxTests/TabManagerUnitTests.swift Outdated
guard let workspace = manager.selectedWorkspace,
let panelId = workspace.focusedPanelId,
let terminalPanel = workspace.terminalPanel(for: panelId) else {
fatalError("Expected selected workspace and focused terminal panel")

@cubic-dev-ai cubic-dev-ai Bot Apr 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: fatalError in a test fixture helper aborts the entire test process instead of failing just the current test. Use XCTFail and return an optional so failures remain isolated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/TabManagerUnitTests.swift, line 915:

<comment>`fatalError` in a test fixture helper aborts the entire test process instead of failing just the current test. Use `XCTFail` and return an optional so failures remain isolated.</comment>

<file context>
@@ -889,6 +889,37 @@ final class TabManagerCloseWorkspacesWithConfirmationTests: XCTestCase {
+        guard let workspace = manager.selectedWorkspace,
+              let panelId = workspace.focusedPanelId,
+              let terminalPanel = workspace.terminalPanel(for: panelId) else {
+            fatalError("Expected selected workspace and focused terminal panel")
+        }
+
</file context>
Fix with Cubic

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (1)
cmuxTests/TabManagerUnitTests.swift (1)

910-916: ⚠️ Potential issue | 🟠 Major

Replace fatalError in test subject builders with per-test failure paths.

Using fatalError here can terminate the entire test run instead of failing only the current test. Please return an optional (or throw) and fail via XCTFail/XCTUnwrap at call sites.

Proposed fix
-    private func makeRuntimeCloseWarningTestSubject() -> (manager: TabManager, workspace: Workspace, panelId: UUID) {
+    private func makeRuntimeCloseWarningTestSubject(
+        file: StaticString = `#filePath`,
+        line: UInt = `#line`
+    ) -> (manager: TabManager, workspace: Workspace, panelId: UUID)? {
         let manager = TabManager()
         guard let workspace = manager.selectedWorkspace,
               let panelId = workspace.focusedPanelId,
               let terminalPanel = workspace.terminalPanel(for: panelId) else {
-            fatalError("Expected selected workspace and focused terminal panel")
+            XCTFail("Expected selected workspace and focused terminal panel", file: file, line: line)
+            return nil
         }

         terminalPanel.surface.setNeedsConfirmCloseOverrideForTesting(true)
         workspace.updatePanelShellActivityState(panelId: panelId, state: .unknown)
         return (manager, workspace, panelId)
     }

-    private func makeLastSurfaceCloseWarningTestSubject() -> (
+    private func makeLastSurfaceCloseWarningTestSubject(
+        file: StaticString = `#filePath`,
+        line: UInt = `#line`
+    ) -> (
         manager: TabManager,
         remainingWorkspace: Workspace,
         closingWorkspace: Workspace,
         closingPanelId: UUID
-    ) {
+    )? {
         let manager = TabManager()
         let remainingWorkspace = manager.tabs[0]
         let closingWorkspace = manager.addWorkspace()
         manager.selectWorkspace(closingWorkspace)

         guard let closingPanelId = closingWorkspace.focusedPanelId,
               let terminalPanel = closingWorkspace.terminalPanel(for: closingPanelId) else {
-            fatalError("Expected focused terminal panel in closing workspace")
+            XCTFail("Expected focused terminal panel in closing workspace", file: file, line: line)
+            return nil
         }

         terminalPanel.surface.setNeedsConfirmCloseOverrideForTesting(true)
         closingWorkspace.updatePanelShellActivityState(panelId: closingPanelId, state: .unknown)
         return (manager, remainingWorkspace, closingWorkspace, closingPanelId)
     }
#!/bin/bash
# Verify remaining fatalError usage and helper call-sites in this test class.
rg -n 'makeRuntimeCloseWarningTestSubject|makeLastSurfaceCloseWarningTestSubject|fatalError\(' cmuxTests/TabManagerUnitTests.swift -C2

Also applies to: 934-937

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 910 - 916, Replace
fatalError in the test helper makeRuntimeCloseWarningTestSubject (and similarly
makeLastSurfaceCloseWarningTestSubject) with a non-terminating failure path:
change the helper to return an optional (or throw) when
selectedWorkspace/focusedPanelId/terminalPanel are missing, and update all call
sites to unwrap with XCTUnwrap or handle the thrown error and call XCTFail on
failure; specifically modify makeRuntimeCloseWarningTestSubject and
makeLastSurfaceCloseWarningTestSubject to return (manager: TabManager,
workspace: Workspace, panelId: UUID)? or throws, and replace direct fatalError
usages in those functions with a nil return or throw, then update tests to use
XCTAssertNotNil/XCTUnwrap or try/catch + XCTFail so a single test fails instead
of terminating the whole test run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 910-916: Replace fatalError in the test helper
makeRuntimeCloseWarningTestSubject (and similarly
makeLastSurfaceCloseWarningTestSubject) with a non-terminating failure path:
change the helper to return an optional (or throw) when
selectedWorkspace/focusedPanelId/terminalPanel are missing, and update all call
sites to unwrap with XCTUnwrap or handle the thrown error and call XCTFail on
failure; specifically modify makeRuntimeCloseWarningTestSubject and
makeLastSurfaceCloseWarningTestSubject to return (manager: TabManager,
workspace: Workspace, panelId: UUID)? or throws, and replace direct fatalError
usages in those functions with a nil return or throw, then update tests to use
XCTAssertNotNil/XCTUnwrap or try/catch + XCTFail so a single test fails instead
of terminating the whole test run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2fcecaf-a0b9-494e-9394-7ccd6ed931d3

📥 Commits

Reviewing files that changed from the base of the PR and between 061f3a9 and 463c386.

📒 Files selected for processing (3)
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • cmuxTests/TabManagerUnitTests.swift

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
cmuxTests/TabManagerUnitTests.swift (1)

948-954: ⚠️ Potential issue | 🟠 Major

Avoid fatalError in fixture builders; fail the current test instead.

fatalError here aborts the entire test run process instead of failing only the invoking test.

Proposed diff
-    private func makeRuntimeCloseWarningTestSubject() -> (manager: TabManager, workspace: Workspace, panelId: UUID) {
+    private func makeRuntimeCloseWarningTestSubject(
+        file: StaticString = `#filePath`,
+        line: UInt = `#line`
+    ) -> (manager: TabManager, workspace: Workspace, panelId: UUID)? {
         let manager = TabManager()
         guard let workspace = manager.selectedWorkspace,
               let panelId = workspace.focusedPanelId,
               let terminalPanel = workspace.terminalPanel(for: panelId) else {
-            fatalError("Expected selected workspace and focused terminal panel")
+            XCTFail("Expected selected workspace and focused terminal panel", file: file, line: line)
+            return nil
         }
@@
-    private func makeLastSurfaceCloseWarningTestSubject() -> (
+    private func makeLastSurfaceCloseWarningTestSubject(
+        file: StaticString = `#filePath`,
+        line: UInt = `#line`
+    ) -> (
         manager: TabManager,
         remainingWorkspace: Workspace,
         closingWorkspace: Workspace,
         closingPanelId: UUID
-    ) {
+    )? {
@@
         guard let closingPanelId = closingWorkspace.focusedPanelId,
               let terminalPanel = closingWorkspace.terminalPanel(for: closingPanelId) else {
-            fatalError("Expected focused terminal panel in closing workspace")
+            XCTFail("Expected focused terminal panel in closing workspace", file: file, line: line)
+            return nil
         }

Also applies to: 972-975

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 948 - 954, Replace the
fatalError calls in the fixture builder makeRuntimeCloseWarningTestSubject (and
the similar occurrence around lines 972-975) with test-friendly failures: call
XCTFail with a descriptive message when manager.selectedWorkspace,
workspace.focusedPanelId, or workspace.terminalPanel(for:) are nil, and then
return a safe fallback (e.g., create a new TabManager and unwrapable defaults or
optionally use guard-let with XCTFail + return a tuple with newly created
manager/workspace/UUID) so the single test fails instead of aborting the whole
test run; reference the makeRuntimeCloseWarningTestSubject function and the
other analogous fixture to locate and change the fatalError usage.
🧹 Nitpick comments (2)
cmuxTests/AppDelegateShortcutRoutingTests.swift (1)

1348-1352: Add failure-path cleanup for the created window.

If this test exits early (e.g., guard/assertion failure), the window can stay open and affect later tests. Consider adding defer cleanup immediately after window creation.

Suggested patch
         let windowId = appDelegate.createMainWindow()
+        defer { closeWindow(withId: windowId) }
         guard let targetWindow = window(withId: windowId) else {
             XCTFail("Expected test window")
             return
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/AppDelegateShortcutRoutingTests.swift` around lines 1348 - 1352,
The test creates a window via appDelegate.createMainWindow() but does not ensure
cleanup if the guard fails; add a defer immediately after obtaining windowId to
close/remove the created window (use the same API that other tests use to tear
down windows) so the window is always cleaned up even on early return; update
the block around createMainWindow(), window(withId:) and targetWindow to
register the cleanup via defer referencing windowId (or targetWindow if
available) before the guard/assertion so later tests aren't affected.
cmuxTests/TabManagerUnitTests.swift (1)

930-946: Use the shared settings key constant instead of a duplicated literal.

warnBeforeClosingTabDefaultsKey duplicates CloseTabWarningSettings.warnBeforeClosingTabKey, which can drift over time.

Proposed diff
-    private let warnBeforeClosingTabDefaultsKey = "warnBeforeClosingTabShortcut"
+    private let warnBeforeClosingTabDefaultsKey = CloseTabWarningSettings.warnBeforeClosingTabKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 930 - 946, The test defines
a duplicated key constant warnBeforeClosingTabDefaultsKey; replace usages with
the shared constant CloseTabWarningSettings.warnBeforeClosingTabKey to avoid
drift — update the setUp and tearDown to use
CloseTabWarningSettings.warnBeforeClosingTabKey when calling
UserDefaults.standard.object(forKey:), removeObject(forKey:) and set(_:forKey:),
and drop the local warnBeforeClosingTabDefaultsKey declaration (keep
originalWarnBeforeClosingTabPreference as-is).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/TabManager.swift`:
- Around line 3991-3995: The shared helper confirmClose currently short-circuits
all callers by returning true when CloseTabWarningSettings.isEnabled() is false;
remove that guard so confirmClose always defers to
confirmCloseHandler/confirmation logic and do not bypass prompts for
non-tab-specific flows. Ensure callers that should respect the user opt-out
(e.g., warnBeforeClosingTab and tab/surface-specific close paths) continue to
check CloseTabWarningSettings.isEnabled() themselves, and leave
workspace/window/pinned/bulk close callers untouched so they still show
confirmations via confirmClose.

---

Duplicate comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 948-954: Replace the fatalError calls in the fixture builder
makeRuntimeCloseWarningTestSubject (and the similar occurrence around lines
972-975) with test-friendly failures: call XCTFail with a descriptive message
when manager.selectedWorkspace, workspace.focusedPanelId, or
workspace.terminalPanel(for:) are nil, and then return a safe fallback (e.g.,
create a new TabManager and unwrapable defaults or optionally use guard-let with
XCTFail + return a tuple with newly created manager/workspace/UUID) so the
single test fails instead of aborting the whole test run; reference the
makeRuntimeCloseWarningTestSubject function and the other analogous fixture to
locate and change the fatalError usage.

---

Nitpick comments:
In `@cmuxTests/AppDelegateShortcutRoutingTests.swift`:
- Around line 1348-1352: The test creates a window via
appDelegate.createMainWindow() but does not ensure cleanup if the guard fails;
add a defer immediately after obtaining windowId to close/remove the created
window (use the same API that other tests use to tear down windows) so the
window is always cleaned up even on early return; update the block around
createMainWindow(), window(withId:) and targetWindow to register the cleanup via
defer referencing windowId (or targetWindow if available) before the
guard/assertion so later tests aren't affected.

In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 930-946: The test defines a duplicated key constant
warnBeforeClosingTabDefaultsKey; replace usages with the shared constant
CloseTabWarningSettings.warnBeforeClosingTabKey to avoid drift — update the
setUp and tearDown to use CloseTabWarningSettings.warnBeforeClosingTabKey when
calling UserDefaults.standard.object(forKey:), removeObject(forKey:) and
set(_:forKey:), and drop the local warnBeforeClosingTabDefaultsKey declaration
(keep originalWarnBeforeClosingTabPreference as-is).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 228279f6-151c-4e77-951f-da10ca44e758

📥 Commits

Reviewing files that changed from the base of the PR and between 463c386 and db10333.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/TabManager.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/TabManagerUnitTests.swift
✅ Files skipped from review due to trivial changes (1)
  • Sources/AppDelegate.swift

Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

4 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/TabManager.swift">

<violation number="1" location="Sources/TabManager.swift:3992">
P2: The new global `CloseTabWarningSettings` check in `confirmClose` disables workspace-close confirmation dialogs as well, not only tab-close warnings.</violation>
</file>

<file name="Sources/AppDelegate.swift">

<violation number="1" location="Sources/AppDelegate.swift:5922">
P2: `warnBeforeClosingTab` is applied too broadly here: it also suppresses explicit window-close confirmation dialogs.</violation>

<violation number="2" location="Sources/AppDelegate.swift:5922">
P1: This guard skips the window-close confirmation entirely when the tab-close warning is disabled. Closing an entire window (e.g. via Cmd+Ctrl+W) may destroy multiple workspaces, but this blanket bypass removes that safety net based on a setting intended only for single-tab Cmd+W closes. Remove this guard and scope the opt-out to tab-specific close paths instead.</violation>

<violation number="3" location="Sources/AppDelegate.swift:5922">
P1: This guard in the shared `confirmClose` helper bypasses **all** confirmation dialogs (pinned-workspace protection, explicit workspace-close, multi-workspace close, "close other tabs") when the tab-close warning is disabled — not just single-tab close prompts. The opt-out should be scoped to the tab/surface-specific call sites, not the generic helper that every confirmation path funnels through.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/TabManager.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated
@austinywang austinywang changed the title Add warnBeforeClosingTab setting Add warnBeforeClosingTab close-warning toggle Apr 13, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/install-clang-wrapper.sh`:
- Around line 98-127: The current relink decision uses only
xcode_count/toolchain_count which can miss cases where entries match in count
but symlink targets are stale; instead, determine need_relink by iterating the
XCODE_DEFAULT_BIN entries and checking per-entry consistency: for each src in
the for-loop (for src in "$XCODE_DEFAULT_BIN"/*) compute name="$(basename
"$src")" and dest="$TOOLCHAIN_BIN/$name", then if dest is missing, not a
symlink, or is a symlink whose readlink result differs from src, set
need_relink=1 (and break) so the subsequent relink block runs; keep the existing
replacement logic that compares current="$(/usr/bin/readlink "$dest" || true)"
and updates/removes dest when mismatched. This uses the existing variables
need_relink, XCODE_DEFAULT_BIN, TOOLCHAIN_BIN and the readlink comparison to
reliably detect and repair stale symlinks.

In `@Sources/cmuxApp.swift`:
- Around line 4998-4999: The current localized subtitle strings for the Warn
Before Quit row ("settings.app.warnBeforeQuit.subtitleOn" and
"settings.app.warnBeforeQuit.subtitleOff") mention both quitting and closing;
update these two localized values so they only describe quit-specific behavior
(e.g., "Show a confirmation before quitting." and "Quit without confirmation.")
and move the close-confirmation wording to the separate close-warning setting
row so the quit control remains quit-scoped; change the String(localized: ...)
usages for the Warn Before Quit row to reference the revised quit-only subtitle
texts.
- Around line 3779-3789: The CloseTabWarningSettings currently aliases its
storage and defaults to QuitWarningSettings
(warnBeforeQuitKey/defaultWarnBeforeQuit), which prevents independent control;
create dedicated storage keys and default values inside CloseTabWarningSettings
(e.g., new static let warnBeforeClosingTabKey and static let
defaultWarnBeforeClosingTab) and update
CloseTabWarningSettings.isEnabled(defaults:) and
CloseTabWarningSettings.setEnabled(_:defaults:) to read/write using those new
keys in UserDefaults instead of delegating to QuitWarningSettings, ensuring
callers of CloseTabWarningSettings (isEnabled and setEnabled) use the
independent close-warning preference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 429d5cd1-b042-41ab-8662-c44c30512e5c

📥 Commits

Reviewing files that changed from the base of the PR and between db10333 and 6f6ad49.

📒 Files selected for processing (10)
  • Resources/Localizable.xcstrings
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AppDelegateShortcutRoutingTests.swift
  • cmuxTests/TabManagerUnitTests.swift
  • cmuxTests/WorkspaceUnitTests.swift
  • scripts/cmux-clang-wrapper.sh
  • scripts/install-clang-wrapper.sh
  • scripts/reload.sh
  • web/data/cmux-settings.schema.json
✅ Files skipped from review due to trivial changes (2)
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • web/data/cmux-settings.schema.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • Resources/Localizable.xcstrings
  • cmuxTests/TabManagerUnitTests.swift

Comment thread scripts/install-clang-wrapper.sh Outdated
Comment thread Sources/cmuxApp.swift
Comment thread Sources/cmuxApp.swift Outdated
Comment thread Sources/cmuxApp.swift
Comment thread Sources/cmuxApp.swift Outdated
Comment thread Sources/TabManager.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

5 issues found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/AppDelegateShortcutRoutingTests.swift">

<violation number="1" location="cmuxTests/AppDelegateShortcutRoutingTests.swift:60">
P2: The fixture now saves/restores the quit-warning preference instead of the close-tab warning preference, so close-warning tests are no longer exercising the intended setting.</violation>

<violation number="2" location="cmuxTests/AppDelegateShortcutRoutingTests.swift:1360">
P2: This test toggles `warnBeforeQuit` instead of `warnBeforeClosingTab`, so it no longer validates the close-warning shortcut behavior described by the test name.</violation>
</file>

<file name="scripts/install-clang-wrapper.sh">

<violation number="1" location="scripts/install-clang-wrapper.sh:101">
P1: Relink detection only compares file counts, so stale symlinks are missed when Xcode changes but counts match.</violation>
</file>

<file name="Sources/cmuxApp.swift">

<violation number="1" location="Sources/cmuxApp.swift:3780">
P1: `warnBeforeClosingTab` is now aliased to `warnBeforeQuit`, so close and quit confirmations cannot be configured independently.</violation>
</file>

<file name="Sources/KeyboardShortcutSettingsFileStore.swift">

<violation number="1" location="Sources/KeyboardShortcutSettingsFileStore.swift:437">
P1: `warnBeforeClosingTab` is being mapped to the quit-warning key, which breaks independent close-vs-quit warning behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread scripts/install-clang-wrapper.sh Outdated
Comment thread Sources/cmuxApp.swift Outdated
Comment thread Sources/KeyboardShortcutSettingsFileStore.swift Outdated
Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift Outdated
Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/AppDelegateShortcutRoutingTests.swift">

<violation number="1" location="cmuxTests/AppDelegateShortcutRoutingTests.swift:1409">
P2: This test asserts the opposite of the new `warnBeforeClosingTab` behavior for Cmd+Ctrl+W, so it can lock in incorrect window-close confirmation logic.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread cmuxTests/AppDelegateShortcutRoutingTests.swift Outdated

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is kicking off a free cloud agent to fix this issue. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b19174f. Configure here.

Comment thread Sources/TabManager.swift Outdated
The close-tab flow needs a regression that demonstrates cmux.json cannot yet suppress the cmux-owned Cmd+W confirmation. The test loads app.warnBeforeClosingTab=false through the same settings file store used at runtime, then closes a terminal surface that normally requires confirmation and expects an immediate close with no prompt.\n\nConstraint: Keep the first commit test-only so CI can show the regression before the fix.\nConfidence: high\nScope-risk: narrow\nDirective: Keep this coverage behavior-level; do not replace it with source-shape assertions.\nTested: Not run locally per repository instruction; expected to fail before the fix in CI.\nNot-tested: Local XCTest execution.
Comment thread Sources/TabManager.swift
The close-tab confirmation decision now combines the existing process/dirty close risk with a cmux-owned app.warnBeforeClosingTab preference. This keeps the close-risk detection centralized and unchanged while allowing configured users to skip the cmux dialog on Cmd+W, tab-strip closes, close-other-tabs flows, runtime surface closes, and pane-close delegation.\n\nConstraint: Build and XCTest must run in CI only for this task; avoid direct xcodebuild.\nRejected: Gate TabManager.confirmClose globally | it would also suppress workspace/window/pinned-workspace confirmations that are not the close-tab setting.\nRejected: Patch only Cmd+W | tab-strip and context-menu closes would keep prompting.\nConfidence: high\nScope-risk: moderate\nDirective: New close-tab prompt paths must call CloseTabConfirmationPolicy instead of checking panelNeedsConfirmClose directly.\nTested: jq empty Resources/Localizable.xcstrings; jq empty web/data/cmux.schema.json; git diff --check.\nNot-tested: Local tests/build per repository and task instruction; CI will validate.
@austinywang
austinywang force-pushed the issue-946-warn-before-closing-tab branch from cce7f01 to 02313ed Compare May 11, 2026 01:20
@austinywang
austinywang merged commit 9c2db8d into main May 11, 2026
25 checks passed

This branch was successfully deployed

2 active (1 outdated) deployments
Preview – cmux — 02313ed7 Deployed May 11, 2026 by vercel[bot]
Preview — b19174f4 Deployed Apr 14, 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.

Option to disable tab close confirmation dialog

1 participant