Plugin docs/examples imply plugin-local hooks, but runtime only executes global hooks.json

Open 💬 4 comments Opened Apr 1, 2026 by isCopyman

Summary

Codex plugin docs/examples imply that plugin-local hooks.json is a supported plugin surface, but the current runtime appears to only load hooks from config-layer hooks.json files (for example ~/.codex/hooks.json).

In practice, an installed plugin can contribute skills / .mcp.json / .app.json, but its plugin-local hooks.json does not seem to run.

Environment

  • Codex CLI: v0.118.0
  • OS: Linux

Repro

  1. Create or install a local plugin with this shape:
  • .codex-plugin/plugin.json
  • hooks.json
  • scripts/runtime-hook.cjs
  1. Put simple lifecycle hooks in hooks.json, for example SessionStart, UserPromptSubmit, Stop, each running a command that writes a file under /tmp or $HOME.
  2. Install/enable the plugin normally so it is present under ~/.codex/plugins/cache/<marketplace>/<plugin>/local and enabled in ~/.codex/config.toml.
  3. Start a new Codex session.

Expected

If plugin-level hooks are supported, the installed plugin's hooks.json should be discovered and executed.

Actual

The plugin installs and can be enabled, but its lifecycle hooks do not run.

If I place the exact same hooks into ~/.codex/hooks.json, they immediately start working.

Why this looks inconsistent

There are several signals that suggest plugin-local hooks are intended to work:

  • The plugins docs/examples mention hooks.json as a plugin companion surface.
  • The public plugin examples include plugin folders with hooks.json.
  • Example manifests include a hooks field pointing at ./hooks.json.

But the current runtime code seems to say otherwise:

  • codex-rs/core/src/plugins/manifest.rs parses plugin manifests and recognizes skills, mcpServers, and apps, but not hooks.
  • codex-rs/hooks/src/engine/discovery.rs discovers hooks by scanning config-layer folders for hooks.json, not installed plugin roots.

So right now the observed behavior is:

  • plugin install/load path: plugin capabilities work for skills/MCP/apps
  • hook runtime path: only config-layer hooks.json is actually executed

Request

Could you clarify which of these is intended?

  1. Plugin-local hooks.json is supposed to work, and this is a runtime bug.
  2. Plugin-local hooks.json is not supported yet, and the docs/examples should be updated to avoid implying support.

If plugin-local hooks are intended to be supported, that would make plugin packaging much cleaner because lifecycle hooks could live inside the plugin bundle instead of requiring a second global ~/.codex/hooks.json installation path.

View original on GitHub ↗

4 Comments

andre-menutole · 4 months ago

A bit of additional technical framing that may help here:

Plugins already act as a packaging/composition boundary for reusable Codex capabilities. Today, an enabled plugin can contribute:

  • skills
  • MCP servers
  • apps

From that perspective, hooks look like a missing runtime capability in the same composition model. Right now, a plugin can package the tools and instructions for a workflow, but not the lifecycle behavior that should accompany that workflow.

That is why this feels more like an incomplete plugin boundary than just a docs mismatch.

A minimal implementation path seems to be:

  1. resolve hooks in plugin manifest loading, alongside skills / mcpServers / apps
  2. expose effective hook config paths for enabled, healthy plugins
  3. pass those paths into the existing hooks discovery/runtime
  4. keep ordering deterministic: config-layer hooks first, plugin hooks after

That would preserve current global/workspace behavior while allowing plugins to package lifecycle behavior together with the rest of the plugin bundle.

zorkary · 3 months ago

Adding a current-version confirmation: I can still reproduce this on Codex 0.128.0-alpha.1 on macOS, specifically in codex exec.

I tested while packaging a small local plugin with both:

  • a stdio MCP server
  • a UserPromptSubmit hook

Results:

  • direct config-level UserPromptSubmit hook works
  • direct MCP registration works
  • plugin-packaged MCP works when present in the installed plugin cache
  • plugin-packaged UserPromptSubmit hook does not execute

I used a marker-file test for the hook command, so this was not just “the model didn’t see the additionalContext”; the plugin hook command itself did not run.

I also tried:

  • hooks/hooks.json with top-level { "hooks": { ... } }
  • bare top-level { "UserPromptSubmit": [...] }
  • manifest hooks field present / absent
  • relative and absolute hook commands
  • [features] codex_hooks = true

All plugin-bundled hook variants failed in codex exec.

One related install-path note: codex plugin marketplace add <local path> registers the marketplace in config but does not appear to install/activate local plugin components by itself; codex plugin marketplace upgrade <name> rejects local marketplaces because it expects a Git marketplace.

oalanicolas · 3 months ago

Confirming reproduction on 0.128.0 (release) on macOS — same setup as @zorkary's report (.codex-plugin/plugin.json with explicit hooks: "./hooks/hooks.json" + hooks/hooks.json + marketplace registered via codex plugin marketplace add . + plugin enabled + [features] codex_hooks = true).

Plugin hooks silent. Same exact command moved to top-level [[hooks.SessionStart]] / [[hooks.UserPromptSubmit]] in ~/.codex/config.toml fires immediately. Marker-file test confirms the hook command never runs from the plugin path — not a "model didn't see additionalContext" issue, the process itself isn't spawned.

@andre-menutole's 4-step plan upthread looks correct to me.

Use case context: filed #21675 for the broader rule-cache proposal that this bug currently blocks distributing as a plugin. Once hooks discovery from plugin manifests works, the entire workaround collapses to a single --uninstall command.

srbryers · 3 months ago

Confirming this is still a blocker on Codex CLI 0.130.0 on macOS.

Use case: packaging SLOPE lifecycle guards as a Codex plugin. SLOPE can generate a .codex-plugin/plugin.json and package the guard dispatcher/scripts cleanly, but the lifecycle hooks still need to live in a config-layer .codex/hooks.json or ~/.codex/hooks.json to be discovered and reviewed by /hooks.

Observed locally:

  • codex features list reports hooks as stable/true
  • plugin_hooks is present but under development/false
  • /hooks discovers config-layer project hooks from ~/Development/fathoms/.codex/hooks.json
  • plugin-local hooks are not available as a usable trust/review surface, so a plugin alone cannot activate the guards

This means a clean slope hook add --level=full --harness=codex implementation currently has to install a shim into global/project hook config rather than shipping lifecycle behavior entirely inside the plugin bundle. The desired fix from @andre-menutole above would resolve that: plugin manifests should expose effective hook config paths to the existing hook discovery/runtime.