You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Binding a chord that is already in use succeeds silently, and which command it then runs depends on unrelated earlier unbinds #152
KeybindingService.BindChord (KeybindingService.cs L60-L87) does not check whether the chord is already bound to another command in the profile. It returns true and both commands stay bound to the same chord. The API gives the caller no way to find out about the conflict or resolve it.
ExecuteChord and FindCommandByChord then return the first match from Profile.FindCommand (Profile.cs L159-L170). "First" here is the enumeration order of the underlying Dictionary<string, Chord>. That order is neither binding order nor any documented rule. Dictionary reuses the slot a removed entry leaves behind, so an UnbindChord of an unrelated command changes which command a shared chord runs.
Reproduction (verified against current main, 03d498c)
varm=newKeybindingManager(dir);foreach(varidinnew[]{"open","save","saveAll"})m.Commands.RegisterCommand(newCommand(id,id));m.CreateDefaultProfile();vark=m.Keybindings;k.BindChord("open",Chord.Parse("Ctrl+O"));k.BindChord("save",Chord.Parse("Ctrl+S"));// k.UnbindChord("open"); // <- toggle this linek.BindChord("saveAll",Chord.Parse("Ctrl+S"));// returns true; "save" is still bound to Ctrl+Sk.ExecuteChord(Chord.Parse("Ctrl+S"));
Without the UnbindChord("open") line, ExecuteChord(Ctrl+S) returns save.
With it, ExecuteChord(Ctrl+S) returns saveAll.
Removing the Ctrl+O binding changes what Ctrl+S does. A settings UI that lets a user "rebind Ctrl+S to Save All" gets a true result, but whether the new binding takes effect depends on the profile's edit history.
Suggested fix / acceptance criteria
Choose one documented policy and enforce it in BindChord (both overloads) and in SetChords:
Replace: binding a chord that another command uses removes the other command's binding. Most editors behave this way when you rebind a shortcut.
Reject: return false, or throw, when the chord is already bound to a different command. Pair it with a lookup such as GetCommandsForChord(chord) or FindConflicts(chord) so a UI can show the conflict.
Whichever policy you choose, do the check and the write under the profile's chord lock (added in #142) so two concurrent binds can't both succeed.
Acceptance:
After BindChord("save", Ctrl+S) followed by BindChord("saveAll", Ctrl+S), the profile has at most one command bound to Ctrl+S, and the result of ExecuteChord(Ctrl+S) follows from the documented policy.
Unbinding an unrelated command never changes what another chord executes. The repro above gives the same result with and without the UnbindChord("open") line.
The interface docs for BindChord state the policy.
Related: #136 (FindCommandByChord and ExecuteChord disagree when a chord is shared) and #116. Both deal with the effects of a shared chord. This issue is about allowing the shared chord to exist in the first place and about the arbitrary order used to resolve it.
Priority: Medium. A chord can end up running a different command depending on edit history, and a settings UI is told the rebind succeeded. A maintainer has to pick a policy (replace or reject) before this can be implemented.
Activity
matt-edmondson commentedon Sep 29, 2026
Triage
KeybindingService.BindChord/SetChordsandProfile.FindCommand. The check-and-write should go under the chord lock from Lock a profile's chords so concurrent binds cannot corrupt them [minor] #142.FindCommandByChordandExecuteChorddisagree on a shared chord). Enforcing one command per chord would likely resolve FindCommandByChord reports an unregistered command for a chord, while ExecuteChord for the same chord runs a different, registered command #136 as well, so fix them together. Also related: KeybindingManager.SetChords can split one batch across two profiles if the active profile changes mid-call #149 (open;SetChordsbatch split), which touches the same code path.Generated by Claude Code