Codex App: native-menu commands ignore all but the first configured keybinding

Open 💬 0 comments Opened Jun 12, 2026 by 0xdevalias

What version of the Codex App are you using (From “About Codex” dialog)?

Codex Desktop 26.608.12217 (bundle build 3722)

What subscription do you have?

Irrelevant

What platform is your computer?

Darwin 25.3.0 arm64 arm

What issue are you seeing?

Codex Desktop accepts and displays multiple keyboard shortcuts for one command, but commands implemented through the native Electron application menu only activate the first configured binding.

For example, this ~/.codex/keybindings.json configuration is accepted:

[
  {
    "command": "searchChats",
    "key": "Command+Shift+F"
  },
  {
    "command": "searchChats",
    "key": "Command+G"
  }
]

The Keyboard Shortcuts settings page shows both shortcuts for Search threads, but only Command+Shift+F, the first entry, opens thread search. Command+G does nothing.

Reversing the two entries and refocusing Codex reverses which shortcut works. This shows that the file is being reloaded, but only the first binding is registered as the native-menu accelerator.

This is inconsistent with commands handled in the webview, which can honor multiple configured bindings.

What steps can reproduce the bug?

  1. Quit Codex or edit ~/.codex/keybindings.json while it is running.
  2. Add two entries for the same native-menu command:

``json
[
{
"command": "searchChats",
"key": "Command+Shift+F"
},
{
"command": "searchChats",
"key": "Command+G"
}
]
``

  1. Start Codex, or switch away from Codex and refocus it so the native application menu is rebuilt.
  2. Open Settings > Keyboard Shortcuts and confirm that both shortcuts are displayed for Search threads.
  3. Press Command+Shift+F. Thread search opens.
  4. Press Command+G. Nothing happens.
  5. Reverse the two entries in keybindings.json.
  6. Refocus Codex.
  7. Observe that Command+G now works and Command+Shift+F no longer works.

The same underlying behavior should affect other commands whose shortcuts are installed as Electron native-menu accelerators.

What is the expected behavior?

Every shortcut displayed and persisted for a command should activate that command.

If Electron permits only one accelerator per menu item, Codex should register additional hidden menu items or another equivalent handler for the remaining bindings. Alternatively, the app should reject multiple bindings for commands that cannot support them, rather than displaying shortcuts that do not work.

Entry order in keybindings.json should not silently determine which advertised shortcut is functional.

Additional information

Packaged implementation evidence

I extracted the installed archive with:

npx --yes @electron/asar extract \
  /Applications/Codex.app/Contents/Resources/app.asar \
  /private/tmp/codex-app-asar-26.608.12217

SHA-256 hashes for the installed archive and relevant extracted chunks:

4545a5b9cc1e244254f444a611f21c49f4f997cb158bab7a2d7397f6954079df  app.asar
3ac681adae4e3251f708b02c8a643f343b04f4aee648d721a20d572499e40b87  .vite/build/src-K8ZToA-n.js
7a02dc7d099958484831a6bce0ef0c9bed847358a6e56354d27573ad8734168a  .vite/build/main-CIL4OHS5.js
d644d0868b6b9ab3cb78045445988b5cc5e966fa23150c961cf64ba44604b444  webview/assets/command-keybindings-aiKFUHFn.js
8e1e44ab185eb1f0d75f3e36972ee11fbeed0f5267990d02f6f3b6babc2016f2  webview/assets/keyboard-shortcuts-settings-BdpA6Kcu.js
795854cdab7c3d9e7061d1e776a62bd7b556505db588da92d1e98bcccfbcaf3e  webview/assets/electron-menu-shortcuts-MJ7p3xz1.js

The relevant implementation details are:

  1. .vite/build/src-K8ZToA-n.js

This chunk owns the command keymap parser and persistence.

  • keybindings.json is parsed as an array of objects shaped as:

``text
{ command: string, key: string | null }
``

  • The effective-keybinding resolver filters all entries with the requested command and returns every non-null key, rather than selecting only one.
  • The update switch explicitly supports:

``text
set
replace
append
remove
clear
reset
``

  • The append case writes the new accelerator after all existing accelerators for that command.

This establishes that repeated command entries and multiple effective accelerators are intentional keymap behavior.

  1. .vite/build/main-CIL4OHS5.js

This chunk constructs the Electron application menu.

Its menu-item helper resolves the full accelerator list, filters unsupported sequence/mouse forms, and then selects exactly one element:

``text
accelerators[index ?? 0]
``

Some commands explicitly create additional hidden menu items using another index. For example, newThread has a normal menu item using index 0 and a hidden item using index 1.

searchChats, however, creates only one hidden native menu item and calls the helper without an index. It therefore registers only accelerator 0, regardless of how many effective bindings the keymap returns.

The same chunk shows that:

  • set-codex-command-keybinding persists the update and calls refreshApplicationMenu();
  • the app also calls refreshApplicationMenu() on browser-window-focus.

This matches the observed behavior where refocusing Codex reloads the reordered file, but still registers only the first searchChats binding.

  1. webview/assets/command-keybindings-aiKFUHFn.js

This chunk requests codex-command-keymap-state and maps every effective accelerator to a displayed shortcut entry. Its query uses a one-minute stale time.

This explains both why the settings page can display multiple bindings and why its display may remain cached briefly after an external file edit.

  1. webview/assets/keyboard-shortcuts-settings-BdpA6Kcu.js

This chunk renders one table row per shortcut entry and sends append, replace, or remove updates for individual accelerators.

The settings UI therefore treats all configured bindings as active assignments, even though the native menu path registers only the indexed menu-item accelerators.

  1. webview/assets/electron-menu-shortcuts-MJ7p3xz1.js

This shared chunk contains the command metadata/default-binding and accelerator-resolution helpers consumed by the webview keybinding state.

Taken together, the packaged code supports multiple bindings end-to-end through parsing, persistence, effective-state calculation, and settings rendering. The loss occurs specifically when native-menu construction selects one accelerator per created Electron menu item.

See also

  • #27835
  • separate UI/discoverability request to make assigning another shortcut explicit; this issue covers the runtime failure after multiple bindings have been assigned

Related but not duplicate:

  • #17114
  • a specific desktop shortcut conflict
  • #24413
  • shortcut remapping/discoverability around archive
  • #3049
  • TUI configurable hotkeys, closed after /keymap shipped

View original on GitHub ↗