Skip to content

codex_hooks do not fire in interactive sessions when configured via repo-local .codex/config.toml #17532

Description

@hahnpierre

Bug Report Draft

Title

codex_hooks do not fire in interactive sessions when configured via repo-local .codex/config.toml

Summary

Repo-local .codex/config.toml appears to be loaded for normal project configuration, but hook configuration inside that file does not result in interactive SessionStart or Stop hooks firing.

Environment

  • Codex CLI version: 0.120.0
  • Installation: npm global package
  • Platform: macOS Apple Silicon
  • Repo uses project-local config: ./.codex/config.toml

Reproduction

  1. Create a repo-local .codex/config.toml with:
hooks = "/absolute/path/to/repo/.codex/hooks.json"

[features]
codex_hooks = true
  1. Create hooks.json with SessionStart and Stop command hooks.
  2. Point the hook commands at scripts that:
    • read hook payload JSON from stdin
    • emit valid hook JSON on stdout
    • write debug artifacts to disk so execution can be confirmed
  3. Start an interactive session with codex from the repo root.
  4. Try to confirm:
    • startup context injection
    • stop-time block/reminder behavior

Expected

  • SessionStart should fire when the interactive session starts
  • Stop should fire when the interactive session exits
  • hook output should affect the live session just like manual hook execution output does

Actual

  • interactive sessions start and stop normally
  • no evidence that SessionStart fired
  • no evidence that Stop fired
  • no startup additionalContext appears in the session rollout log
  • no stop block/reminder occurs

Important Control Result

The same hook scripts work when run manually:

  • SessionStart script emits valid JSON with hookSpecificOutput.additionalContext
  • Stop script emits valid JSON with decision = "block" and a reason
  • debug artifacts are written correctly

This suggests the problem is not the script payload format itself, but that repo-local hook config is not actually being honored by interactive hook execution.

Notes

  • Moving hooks = ".../hooks.json" under [features] is invalid and causes a TOML type error. The correct placement is top-level.
  • The repo-local config format itself parses cleanly once hooks is top-level.
  • The issue affects at least both SessionStart and Stop, which suggests a config-loading or hook-registration problem rather than an event-specific matcher issue.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    hooksIssues related to event hooks
    configIssues involving config.toml, config keys, config merging, or config updates
    on Apr 12, 2026
  2. github-actions commented on Apr 12, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. removed
    CLIIssues related to the Codex CLI
    configIssues involving config.toml, config keys, config merging, or config updates
    on Apr 12, 2026
  4. driptaroop commented on Apr 13, 2026

    @driptaroop

    can confirm. faced the same issue.

  5. sly-codechum commented on Apr 13, 2026

    @sly-codechum

    Same

  6. abhinav-oai commented on Apr 15, 2026

    @abhinav-oai
    Contributor

    Just setup a Stop hook locally and I'm not able to repro

    Here's my local setup:

    ─────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
         │ File: .codex/hooks.json
    ─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       1 │ {
       2 │   "hooks": {
       3 │     "Stop": [
       4 │       {
       5 │         "hooks": [
       6 │           {
       7 │             "type": "command",
       8 │             "command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/stop_keep_going.py\"",
       9 │             "timeout": 30,
      10 │             "statusMessage": "blocking end of turn"
      11 │           }
      12 │         ]
      13 │       }
      14 │     ]
      15 │   }
      16 │ }
    ─────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    ─────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
         │ File: .codex/config.toml
    ─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       1 │ [features]
       2 │ codex_hooks = true
    ─────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    ─────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
         │ File: .codex/hooks/stop_keep_going.py
    ─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       1 │ #!/usr/bin/env python3
       2 │
       3 │ import json
       4 │ import sys
       5 │ import time
       6 │
       7 │
       8 │ def main() -> int:
       9 │     json.load(sys.stdin)
      10 │     json.dump({"decision": "block", "reason": "keep going"}, sys.stdout)
      11 │     sys.stdout.write("\n")
      12 │     return 0
      13 │
      14 │
      15 │ if __name__ == "__main__":
      16 │     raise SystemExit(main())
    ─────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    

    As well as how my conversation looks

    › hi
    
    
    • Hi.
    
    • Stop hook (blocked)
      feedback: keep going
    

    Would be super helpful if you could attach more details, thank you!


    Also FWIW we don't support a custom path to a hooks.json via top-level hooks field in the config.toml. Codex will resolve the hooks.json by looking in the same directories as any enabled config layers.

  7. leorrose commented on Apr 16, 2026

    @leorrose

    same

  8. coladapo commented on May 3, 2026

    @coladapo

    +1 — confirmed reproducible on Codex CLI 0.128.0 (macOS, ChatGPT auth). Adding empirical detail in case it helps narrow the cause.

    Test setup

    Minimal probe hook at /tmp/codex-probe.sh:

    #!/bin/bash
    date -u +"%Y-%m-%dT%H:%M:%SZ probe fired" >> /tmp/codex-probe.log
    echo '{"systemMessage":"PROBE WORKED"}'

    Registered at ~/.codex/hooks.json:

    {
      "hooks": {
        "SessionStart": [
          {
            "matcher": "startup|resume",
            "hooks": [{ "type": "command", "command": "/tmp/codex-probe.sh", "statusMessage": "PROBE…" }]
          }
        ]
      }
    }

    ~/.codex/config.toml has [features] codex_hooks = true (also confirmed default-true in v0.128.0 source).

    Results

    Invocation Probe log written? systemMessage shown? codex_hooks debug log
    codex exec 'echo test' ✅ yes n/a (non-interactive) hook: SessionStart + hook: SessionStart Completed
    codex (interactive TUI) ❌ no ❌ no (no hook log lines)

    So the discovery + parsing path works (since exec finds and runs the same hooks.json), but the interactive TUI startup path skips hook execution entirely. Same ~/.codex/hooks.json, same ~/.codex/config.toml, same probe script — only the launch mode differs.

    Reproduction

    1. Install probe + config above
    2. codex exec --skip-git-repo-check 'echo' → /tmp/codex-probe.log gets a line
    3. codex (interactive) → no log entry, no system message
    4. Both layers tested (user ~/.codex/ and project ./.codex/) — same result

    This affects cross-platform tooling that targets all three of Claude Code / Gemini CLI / Codex via SessionStart hooks (e.g. memory injection at session start). Header + context render fine on Claude and Gemini interactive sessions, silently fail on Codex interactive.

    Happy to PR a fix if you can point at where the interactive TUI startup path should call into the hook engine.

  9. oxysoft commented on May 8, 2026

    @oxysoft

    Indexed this hook ticket in the umbrella tracker: #21753

    Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.

  10. 8 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghooksIssues related to event hooks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions