镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Apps that use the default data directory share one %APPDATA%/Keybinding folder and overwrite each other's profiles #249

Open

Description

@matt-edmondson

What's wrong

When no data directory is passed, every entry point falls back to the same path with nothing app-specific in it:

  • Keybinding/Extensions/ServiceCollectionExtensions.cs:25 (AddKeybinding())
  • Keybinding/Extensions/ServiceCollectionExtensions.cs:74 (AddKeybindingScoped())
  • Keybinding/Services/KeybindingManagerFactory.cs:51 (CreateManager() fallback)
  • Keybinding/Models/KeybindingConfiguration.cs:17

Each is Path.Combine(Environment.GetFolderPath(SpecialFolder.ApplicationData), "Keybinding"). That resolves to %APPDATA%\Keybinding on Windows and ~/.config/Keybinding on Linux for every consumer of the library. USAGE_EXAMPLES.md recommends calling AddKeybinding() with no directory.

Failure scenario

Two unrelated apps, A and B, both call services.AddKeybinding() and CreateDefaultProfile(), which uses id "default".

  1. A binds editor.save to Ctrl+S and saves.
  2. B binds game.jump to Space and saves.
    • JsonKeybindingRepository.SaveProfileAsync runs RemoveAll(p => p.Id == profile.Id), so B's profile replaces A's "default" profile and all of A's bindings.
    • B also writes its own active profile to the shared active-profile.json.
    • The command merge in KeybindingManager keeps stored commands it did not load, so A's commands stay in the shared commands.json.
  3. A restarts and calls InitializeAsync:
    • Ctrl+S is unbound, and the "default" profile holds game.jump on Space.
    • B's commands appear in A's GetAllCommands, SearchCommands and GetSummary().TotalCommands.

The user's bindings are lost and nothing reports it.

Suggested fix / acceptance criteria

  • Define the default directory once, for example in Constants, and use that definition at all four call sites.
  • Make the default app-specific. For example, Path.Combine(ApplicationData, Assembly.GetEntryAssembly()?.GetName().Name ?? "Keybinding", "Keybinding"), or an appName parameter on AddKeybinding/CreateManager. Making the directory required is the other option.
  • Document the default location in the README.
  • Add a test: two configurations with different entry or app names resolve to different directories, and the default path contains the app name.
  • Optional: when the old shared folder exists, log it or migrate from it once, so existing single-app users keep their bindings.

Related but different: #93 proposes moving persistence onto ktsu.AppDataStorage, which would also scope storage per app. This issue is the narrower data-loss bug in the current default.

Activity

  1. matt-edmondson commented on Nov 12, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    • Category: Bug
    • Priority: High. Any two apps on the same machine that follow the documented AddKeybinding() usage overwrite each other's "default" profile and mix their commands. The user's bindings are lost with no error.
    • Area / suggested owner: the library's persistence defaults (ServiceCollectionExtensions, KeybindingManagerFactory, KeybindingConfiguration), plus Constants, README and USAGE_EXAMPLES.md.
    • Duplicates: none. Delegate profile/command JSON persistence to ktsu.AppDataStorage #93 (move to ktsu.AppDataStorage) would fix this as a side effect, but it is a larger change. This narrower fix can land first.
    • In progress: no open PR addresses this. Fold browser and Silk.NET letter and digit key names into the plain key [patch] #219 and Bump System.Text.Json from 10.0.14 to 11.0.0 #241 are unrelated.
    • Note: changing the default path is a behaviour change for existing single-app users, whose bindings would appear to vanish. The optional one-time migration or log in the last checkbox is therefore worth doing, and the release should be at least a minor bump.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions