Repository navigation
Description
What's wrong
PR #190 (d77c244) fixed #189 by having KeybindingManager.CreateDefaultProfile return early whenever any profile already exists (Keybinding/KeybindingManager.cs:228-231):
if (Profiles.GetAllProfiles().Count > 0)
{
return null;
}That return comes before any activation. The only activation path left runs when the profileId profile itself exists (lines 218-226).
So consider this state: "default" has been deleted, other profiles remain, and none of them is active. In that state the documented startup call returns null and leaves no profile active. That is the #174 symptom again, reached by a different path.
There are several ways to end up with no active profile:
ProfileManager.DeleteProfileclears the active id when the active profile is deleted (Keybinding/Services/ProfileManager.cs:69-72).InitializeAsyncleaves nothing active whenactive-profile.jsonis missing or blank.InitializeAsyncalso leaves nothing active when that file names an unknown id.
The XML doc for activation promises the opposite: "a startup call recovers from a deleted or unset active profile without overriding the one the user chose".
Reproduction (main @ 6d9183d, net10.0)
using (var m = new KeybindingManager(dir)) {
await m.InitializeAsync(); m.CreateDefaultProfile();
m.Profiles.CreateProfile("vim", "Vim"); m.Profiles.CreateProfile("emacs", "Emacs");
m.Profiles.SetActiveProfile("vim");
m.Profiles.DeleteProfile("default");
m.Profiles.DeleteProfile("vim"); // active cleared; "emacs" remains
await m.SaveAsync();
}
using (var m = new KeybindingManager(dir)) { // next launch, README startup sequence
await m.InitializeAsync();
var r = m.CreateDefaultProfile();
m.RegisterCommands([new Command("file.save", "Save")]);
m.Keybindings.BindChord("file.save", Chord.Parse("Ctrl+S"));
}Observed: returned=null active=none profiles=[emacs] bind=False.
The same happens on every later launch. BindChord returns false, ExecuteChord returns null, and nothing recovers on its own.
Suggested fix / acceptance criteria
- When profiles exist but none is active,
CreateDefaultProfile(..., ProfileActivation.Activate)activates one deterministically and does not recreatedefault. One possible rule: theprofileIdprofile if it exists, otherwise the only profile, otherwise the first by ordinal id. Document whichever rule is chosen in the method's XML docs. - The Calling CreateDefaultProfile() at startup, as documented, recreates a deleted "default" profile and makes it the active profile on every launch #189 guarantees still hold: a deleted
defaultis not recreated while other profiles exist, and an active profile is never replaced. - A regression test covers the two-launch sequence above, next to
ManagerStartupTests/DefaultProfileReactivationTests. On launch 2 a profile is active andBindChordsucceeds.
Activity
matt-edmondson commentedon Oct 29, 2026
Triage
defaultis deleted and the active profile is gone, every later launch through the documented startup sequence ends with no active profile.BindChordreturns false andExecuteChordreturns null. Nothing recovers on its own, and the XML docs promise the opposite. The state is reachable through ordinary profile management: delete the active profile, or haveactive-profile.jsonmissing or stale.KeybindingManager.CreateDefaultProfile(the early return at lines 228-231) and its interaction withProfileManager.DeleteProfile/InitializeAsync.DefaultProfileId/Nameconfiguration is ignored; whatever rule is chosen here should be consistent with that config if KeybindingConfiguration's AutoSave, AutoSaveIntervalMilliseconds and DefaultProfileId/Name have no effect: nothing in the library reads IKeybindingConfiguration #145 is ever wired up. No open PR covers this.default. One rule: the requestedprofileId, otherwise the only profile, otherwise the first by ordinal id.ManagerStartupTests/DefaultProfileReactivationTests.Generated by Claude Code