Stop hooks need host-level aggregation across plugins

Open 💬 0 comments Opened Jul 11, 2026 by tennisonchan

What happened?

When multiple plugins register Stop hooks, Codex does not appear to expose a clear host-level aggregation contract for their results.

In a real setup I had at least two independent plugin Stop hooks enabled:

  • a review gate hook that can block finalization when changes are requested
  • a task/acceptance hook that can block finalization when required task closeout is incomplete

The failure mode I am trying to avoid is: one Stop hook requests continuation, but another independent Stop blocker is not preserved or presented in the same effective continuation decision. Individual plugins should not have to know about each other to coordinate this.

Expected behavior

The Codex hook runner / plugin manager should treat plugin Stop hooks as leaf policies and aggregate them generically:

  • run all registered Stop hooks for the event
  • preserve hook identity/source for attribution
  • return a single aggregate block if any child hook blocks
  • include all blocking reasons in deterministic order
  • keep advisory/system messages on their non-blocking channel unless a separate model-visible advisory channel is explicitly defined
  • distinguish policy blocks from execution failures such as invalid JSON, nonzero exit, and timeout
  • respect each child hook's declared timeout
  • propagate recursion signals such as stop_hook_active
  • provide enough diagnostics to answer which hooks ran and what each decided

Why this should be host-level

This should not be fixed by hardcoding one plugin to know about another plugin. Plugins should remain independent leaf policies. The host/plugin-manager Stop execution layer is the only layer that can correctly own ordering, aggregation, timeout handling, failure handling, and diagnostics across all registered hooks.

Environment / evidence

  • Codex CLI: codex-cli 0.144.1
  • Platform: macOS arm64
  • Plugin manifests declare Stop hooks independently
  • Installed hook state expands to per-plugin Stop hook entries such as:
  • review-loop@review-loop:hooks/codex-hooks.json:stop:0:0
  • kernel@agent-kernel-local:hooks/hooks.json:stop:0:0

I have not captured a minimal two-hook runtime trace yet, so I am not claiming a specific internal short-circuit implementation. The actionable request is to define and enforce the aggregation contract for multiple independent Stop hooks so blockers cannot be hidden or dropped by ordering/runner behavior.

Related but not the same: #27052 asks for better hook diagnostics. This issue is specifically about multi-Stop-hook aggregation semantics.

View original on GitHub ↗