Skip to content

fix: avoid recursive dispatch_once deadlock when settings.json has active shortcuts - #3413

Closed
maucher wants to merge 1 commit into
manaflow-ai:mainfrom
maucher:fix/settings-file-shortcut-deadlock
Closed

maucher wants to merge 1 commit into
manaflow-ai:mainfrom
maucher:fix/settings-file-shortcut-deadlock

Conversation

@maucher

@maucher maucher commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fixes a crash-on-launch (EXC_BREAKPOINT / SIGTRAP) that affects any user with an active shortcuts.bindings entry in ~/.config/cmux/settings.json.
  • Root cause: parseShortcutBindingValue called action.normalizedRecordedShortcut() during CmuxSettingsFileStore.shared initialization, which internally called conflictingAction(), which accessed KeyboardShortcutSettings.settingsFileStore — the very singleton whose dispatch_once was still on the stack — causing a recursive lock trap (BUG IN CLIENT OF LIBDISPATCH: trying to lock recursively).
  • Fix: replace with action.resolvedRecordedShortcutIgnoringConflicts(), which retains format validation (numbered digits, system-wide hotkey modifier) but skips the conflict check. Skipping conflicts during file parsing is also semantically correct — the file defines the overrides, so checking against the not-yet-initialized store is meaningless. The interactive conflict check in the Settings UI is unaffected.

Fixes #3412

Testing

  • Reproduced the crash locally with an active shortcuts.bindings entry in settings.json on macOS 15.7.5.
  • Built locally with the fix applied — app launches successfully with the same settings.json.
  • Workaround confirmed: commenting out the active shortcuts block unblocks the nightly without the fix.

Demo Video

No UI change — crash fix only.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by cubic

Fixes a launch crash when ~/.config/cmux/settings.json has active shortcuts.bindings by skipping conflict checks during startup parsing to avoid a recursive dispatch_once deadlock. Accepts the raw .showHideAllWindows shortcut, validates format, and trims inline comments; the Settings UI still checks conflicts after init. Fixes #3412.

  • Bug Fixes
    • Replaced action.normalizedRecordedShortcut(...) with action.resolvedRecordedShortcutIgnoringConflicts(...) and special-cased .showHideAllWindows to avoid re-entering KeyboardShortcutSettings.settingsFileStore during CmuxSettingsFileStore init.
    • Trimmed comments in parseShortcutBindingValue so entries with trailing comments parse correctly.

Written for commit 91416c8. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Restored the intended behavior for the "Show/Hide All Windows" shortcut to prevent unintended conflicts.
    • Improved parsing, normalization and fallback handling for other keyboard shortcuts to make bindings more reliable and consistent.

@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown

@maucher is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented May 1, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d405f92-ab79-498d-9417-edaa2320489e

📥 Commits

Reviewing files that changed from the base of the PR and between 492418a and 91416c8.

📒 Files selected for processing (1)
  • Sources/KeyboardShortcutSettingsFileStore.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/KeyboardShortcutSettingsFileStore.swift

📝 Walkthrough

Walkthrough

parseShortcutBindingValue now special-cases Action.showHideAllWindows to return the parsed shortcut immediately; for other actions it uses resolvedRecordedShortcutIgnoringConflicts and returns the normalized shortcut only when the resolver result is .accepted, otherwise it falls back to the prior raw-or-nil behavior.

Changes

Cohort / File(s) Summary
Keyboard Shortcut Parsing
Sources/KeyboardShortcutSettingsFileStore.swift
parseShortcutBindingValue bypasses normalization for Action.showHideAllWindows; for other actions it calls resolvedRecordedShortcutIgnoringConflicts and returns the resolved .accepted shortcut, otherwise falls back to existing behavior (raw shortcut or nil when usesNumberedDigitMatching applies). Prevents recursive normalization during initialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I hopped through keys and tangled threads,
Skipped a norm where recursion treads,
Accepted presses stand their ground,
Else fall back to what was found,
A little hop — fewer loops ahead!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: avoiding a recursive dispatch_once deadlock when settings.json contains active shortcuts.
Description check ✅ Passed The PR description covers all required template sections: Summary explains what changed and why, Testing documents reproduction and verification, and Checklist shows most items completed, though behavior change tests were not added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@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

🤖 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/KeyboardShortcutSettingsFileStore.swift`:
- Around line 848-856: The current early-return logic uses
resolvedRecordedShortcutIgnoringConflicts(...) and then falls back to returning
the original shortcut when rejected, which lets invalid .showHideAllWindows
bindings slip through; update the fallback to also drop rejected
.showHideAllWindows the same way storedShortcutForPersistence(...) does by
returning nil for actions that use numbered digit matching or are
.showHideAllWindows when the resolvedRecordedShortcutIgnoringConflicts(...) did
not accept the shortcut (i.e., change the final return to check
action.usesNumberedDigitMatching || action == .showHideAllWindows and return nil
in that case).
🪄 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: 5f4a1858-041e-4181-9849-c5e3f9dbcec0

📥 Commits

Reviewing files that changed from the base of the PR and between 6e69f2c and 782257b.

📒 Files selected for processing (1)
  • Sources/KeyboardShortcutSettingsFileStore.swift

Comment thread Sources/KeyboardShortcutSettingsFileStore.swift Outdated
@greptile-apps

greptile-apps Bot commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a launch crash (EXC_BREAKPOINT / SIGTRAP) caused by a recursive dispatch_once deadlock in CmuxSettingsFileStore.shared initialization when shortcuts.bindings is present in settings.json. The fix correctly short-circuits the call chain for .showHideAllWindows before it can re-enter the still-initializing singleton, and replaces the conflict-checking normalizedRecordedShortcut with resolvedRecordedShortcutIgnoringConflicts for all other actions.

Confidence Score: 5/5

Safe to merge — targeted fix with no behavioral regression.

The early-return for .showHideAllWindows is provably equivalent to the original non-deadlock path: usesNumberedDigitMatching is false for that action, so any .rejected result from normalizedSystemWideHotkeyShortcutResult would have fallen through to return shortcut anyway, and a .accepted result also returns shortcut unchanged. The fix simply short-circuits the re-entrant store access before it deadlocks, producing the same stored value. No P0 or P1 issues found.

No files require special attention.

Important Files Changed

Filename Overview
Sources/KeyboardShortcutSettingsFileStore.swift Adds an early return for .showHideAllWindows to cut the recursive dispatch_once deadlock path, and switches from normalizedRecordedShortcut to resolvedRecordedShortcutIgnoringConflicts for other actions; behaviorally equivalent to the pre-crash path since showHideAllWindows.usesNumberedDigitMatching == false means .rejected cases already fell back to returning the raw shortcut.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[parseShortcutBindingValue called during dispatch_once init] --> B{action == .showHideAllWindows?}
    B -- Yes, NEW --> C[return shortcut immediately\nno store re-entry]
    B -- No --> D[resolvedRecordedShortcutIgnoringConflicts]
    D -- accepted --> E[return normalized shortcut]
    D -- rejected --> F{usesNumberedDigitMatching?}
    F -- true --> G[return nil, discard binding]
    F -- false --> H[return raw shortcut]
    B -- Yes, BEFORE FIX --> X[normalizedSystemWideHotkeyShortcutResult\nreservedSystemWideHotkeyShortcuts\nshortcut for action\noverride for action\nCmuxSettingsFileStore.shared]
    X --> Z[recursive dispatch_once trap]
Loading

Reviews (2): Last reviewed commit: "fix: avoid recursive dispatch_once deadl..." | Re-trigger Greptile

Comment thread Sources/KeyboardShortcutSettingsFileStore.swift
…tive shortcuts

During CmuxSettingsFileStore.shared initialization, parseShortcutBindingValue
called action.normalizedRecordedShortcut(), which called conflictingAction(),
which accessed KeyboardShortcutSettings.settingsFileStore — the very singleton
whose dispatch_once was still on the stack. This caused a recursive lock trap:

  BUG IN CLIENT OF LIBDISPATCH: trying to lock recursively

Fix: accept the raw shortcut during settings file parsing; skip all conflict
checks (including showHideAllWindows system-wide hotkey conflicts) which would
re-enter the not-yet-initialized store. The Settings UI validates with full
conflict detection post-init. Also trims comments from parsed shortcut values.

Fixes manaflow-ai#3412

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@maucher
maucher force-pushed the fix/settings-file-shortcut-deadlock branch from 492418a to 91416c8 Compare May 1, 2026 20:06
@maucher

maucher commented May 2, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@maucher

maucher commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

The fix from this PR has been superseded by the upstream implementation in normalizedSettingsFileShortcut (merged into main), which solves the same dispatch_once deadlock more generally by passing checkingSystemWideConflicts: false for all settings-file parsing — covering showHideAllWindows and any future similar cases. Closing as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant