Hook settings page shows generic Hook N labels instead of hook names or commands

Open 💬 2 comments Opened May 30, 2026 by majiayu000

What happened?

In the Codex desktop app Hooks settings page, configured hooks are displayed only as generic rows such as Hook 1, Hook 2, Hook 3 (localized as 钩子 1, 钩子 2, etc.).

When several hooks are registered under the same lifecycle event, it is hard to tell which toggle belongs to which hook without inspecting the underlying config manually.

Example from my current ~/.codex/hooks.json:

PreToolUse Hook 1: matcher=Bash  command=... run-hook-codex.sh vibeguard-pre-bash-guard.sh
PreToolUse Hook 2: matcher=Edit  command=... run-hook-codex.sh vibeguard-pre-edit-guard.sh
PreToolUse Hook 3: matcher=Write command=... run-hook-codex.sh vibeguard-pre-write-guard.sh

PostToolUse Hook 1: matcher=Edit       command=... run-hook-codex.sh vibeguard-post-edit-guard.sh
PostToolUse Hook 2: matcher=Write      command=... run-hook-codex.sh vibeguard-post-write-guard.sh
PostToolUse Hook 3: matcher=Bash       command=... run-hook-codex.sh vibeguard-post-build-check.sh
PostToolUse Hook 4: matcher=Edit|Write command=... run-hook-codex.sh vibeguard-post-build-check.sh

Stop Hook 1: command=... run-hook-codex.sh vibeguard-stop-guard.sh
Stop Hook 2: command=... run-hook-codex.sh vibeguard-learn-evaluator.sh
Stop Hook 3: command=... remem summarize

The UI only shows Hook 1, Hook 2, etc. for these rows, so the user cannot tell at a glance whether a row is vibeguard-pre-bash-guard.sh, vibeguard-post-build-check.sh, remem summarize, etc.

Expected behavior

The Hooks settings page should show a human-readable label per hook row. Good fallbacks would be, in order:

  1. An explicit hook name / displayName if the config schema supports it.
  2. The command basename or script name, for example vibeguard-pre-bash-guard.sh.
  3. The matcher plus command basename, for example Bash · vibeguard-pre-bash-guard.sh.
  4. Only fall back to Hook {index} if no better label can be derived.

This would make the toggles understandable and reduce the chance of disabling or trusting the wrong hook.

Actual behavior

All rows under an event use the fallback label Hook {index}. In zh-CN this appears as 钩子 {index}.

I inspected the packaged webview bundle for the installed app and found the hook row title path using the settings.hooks.event.fallbackHookTitle string (Hook {index}). I did not see the settings page rendering a command/script-derived label for the row.

Steps to reproduce

  1. Configure multiple hooks in ~/.codex/hooks.json under the same lifecycle event, for example several PreToolUse, PostToolUse, or Stop hooks.
  2. Open Codex desktop app.
  3. Go to Settings → Hooks.
  4. Expand a hook event section.
  5. Observe that rows are labeled only Hook 1, Hook 2, etc.

Environment

  • Codex desktop app: 26.527.31326 (build 3390)
  • Bundled CLI: codex-cli 0.135.0-alpha.1
  • OS: macOS 26.2 (25C56)
  • Locale observed: zh-CN, but the issue appears to come from the generic fallback label rather than the locale itself.

Notes

This does not appear to affect hook execution. It is a settings UI discoverability issue: the active hooks work, but the user cannot identify them from the settings page labels.

View original on GitHub ↗

2 Comments

mickitty0511 · 2 months ago

Thanks for posting this feature request. Same on Windows, I upvoted

ojhurst · 2 months ago

I can confirm this is still happening on macOS with a newer desktop build:

  • Codex desktop app: 26.601.21317 build 3511
  • Bundled CLI: codex-cli 0.136.0-alpha.2
  • OS: macOS

My ~/.codex/hooks.json has multiple hooks under SessionStart and one under UserPromptSubmit. I tried adding explicit name fields like this:

{
  "name": "Inject Codex voice context",
  "type": "command",
  "command": "python3 ~/.codex/hooks/voice-session-context.py",
  "timeout": 2,
  "statusMessage": "Checking Codex voice"
}

and:

{
  "name": "Start visible-message speaker",
  "type": "command",
  "command": "python3 ~/.codex/hooks/speak-visible-agent-messages.py --start",
  "timeout": 2,
  "statusMessage": "Starting Andrew Neural for visible Codex messages"
}

The JSON parses cleanly and the hook rows still render as Hook 1, Hook 2, etc. The statusMessage is only visible after expanding the row, so it does not solve the scanability problem in the main list.

I also inspected the packaged webview bundle for this build. The relevant files appear to be:

  • hooks-settings-copy-DsWrJ1mr.js, which defines settings.hooks.event.fallbackHookTitle as Hook {index}
  • hooks-settings-CDWLXYuK.js, where the row label path calls the fallback title helper for each hook row

I did not see the row title path reading name, displayName, label, or deriving a basename from command. So this looks like a current UI/model limitation rather than a config typo.

A supported name / displayName field would be ideal, with command basename as a fallback.