Profile config causes global hooks.json hooks to be discovered and executed twice

Resolved 💬 1 comment Opened Jun 1, 2026 by qsunyi Closed Jun 11, 2026

Summary

When launching Codex with a v2 profile (for example codex --profile gpt), every global hook in ~/.codex/hooks.json is discovered and executed twice.

This is not only duplicated UI output: the hook command is actually spawned twice.

Environment

  • Codex CLI: codex-cli 0.135.0
  • Platform: macOS
  • Launch command: codex --profile gpt
  • Global hooks file: ~/.codex/hooks.json
  • Profile file: ~/.codex/gpt.config.toml

Reproduction

  1. Create a base user config:

``toml
# ~/.codex/config.toml
model = "gpt-5.5"
``

  1. Create a v2 profile in the same Codex home:

``toml
# ~/.codex/gpt.config.toml
model = "gpt-5.5"
``

  1. Add a global hook in ~/.codex/hooks.json, for example a UserPromptSubmit or SessionStart command that appends its PID to a temp file.
  1. Start Codex with the profile:

``bash
codex --profile gpt
# or
codex exec --profile gpt ...
``

Observed behavior

The same hook is run twice for a single event.

In my verification, I temporarily added a probe to an existing UserPromptSubmit hook script. A single user prompt produced two log lines with different PIDs:

hook: UserPromptSubmit
hook: UserPromptSubmit
hook: UserPromptSubmit Completed
hook: UserPromptSubmit Completed
matching_lines 2
1: avm-existing-hook-proof-1780331979993 pid=77018
2: avm-existing-hook-proof-1780331979993 pid=77019

The same duplicated behavior was also visible for other hook events, including SessionStart, PreToolUse, PostToolUse, and Stop.

Expected behavior

A global ~/.codex/hooks.json hook should be discovered and executed once per matching event, even when a profile is active.

Suspected cause

The profile appears to create an additional user config layer. Both the base user config and profile config live under the same Codex home, so both layers resolve hooks_config_folder() to the same directory (~/.codex). Hook discovery then loads the same ~/.codex/hooks.json once per layer and does not deduplicate by source path.

Relevant code path appears to be in codex-rs/hooks/src/engine/discovery.rs:

for layer in config_layer_stack.get_layers(...) {
    let json_hooks = load_hooks_json(layer.hooks_config_folder().as_deref(), &mut warnings);
    ...
    for (source_path, hook_events) in [json_hooks, toml_hooks].into_iter().flatten() {
        append_hook_events(...);
    }
}

Because both user layers have the same hooks config folder, the same hooks.json source path is appended twice.

Impact

This doubles the runtime cost of hooks when using profiles. It is especially noticeable for hooks that:

  • perform memory/vector recall on UserPromptSubmit,
  • run before/after every tool call (PreToolUse / PostToolUse),
  • write logs or index data,
  • inject additional context into the model.

It can also duplicate model-visible hook context, increasing token usage and potentially causing repeated or overweighted context.

Possible fixes

  • Deduplicate loaded hook declaration sources by canonical source_path during hook discovery; or
  • Treat profile user layers as config overrides that do not independently rediscover sibling hooks.json; or
  • Explicitly load global ~/.codex/hooks.json once per effective config stack.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗