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
Concurrent CreateProfile calls for the same id can return a Profile that is never stored, so its bindings are silently lost #112
ProfileManager.CreateProfile (Keybinding/Services/ProfileManager.cs:~45-54) does a check-then-act and ignores the result of TryAdd:
if(_profiles.TryGetValue(normalizedId,outProfile?existingProfile))returnexistingProfile;ProfilenewProfile=new(normalizedId,name.Trim(),description);_profiles.TryAdd(normalizedId,newProfile);// result ignoredreturnnewProfile;
When two threads create the same id at once, the thread whose TryAdd fails still returns its own Profile, which is not in _profiles. The XML doc promises "the created profile, or existing profile if it already exists", and CLAUDE.md describes the services as thread-safe.
Failure scenario (reproduced)
Two threads behind a Barrier both call CreateProfile("p", "P"), repeated 2000 times. In 297 trials the returned instance was not ReferenceEquals to GetProfile("p").
Every SetChord on the orphaned instance is invisible to GetProfile, and SaveAsync never persists it.
This is separate from #109, which is about concurrent BindChord mutating a profile's inner Dictionary. This race is at the profile-map level.
Priority: Medium. It breaks the documented thread-safety guarantee and silently loses bindings, but it needs two threads creating the same profile id at the same moment. That is uncommon in practice, although the reproduction shows it is easy to hit when it does happen.
Area:ProfileManager.CreateProfile
Suggested assignment: Keybinding maintainer, services area
Notes: The GetOrAdd fix is a one-liner and low risk. GetOrAdd's factory can run more than once under contention, but only one result is stored and returned to every caller, which is exactly the fix needed here.
Activity
matt-edmondson commentedon Sep 26, 2026
Triage
ProfileManager.CreateProfileDictionaryrace). Related to RenameProfile clears the description when none is passed, and replaces the Profile object so held references silently stop persisting #113 (RenameProfileswap). All three areProfileManagerconcurrency holes and are worth fixing in one pass.Notes: The
GetOrAddfix is a one-liner and low risk.GetOrAdd's factory can run more than once under contention, but only one result is stored and returned to every caller, which is exactly the fix needed here.Generated by Claude Code