What Is the Purpose of Hook Files in the Codex Plugin?

The hook files in the Codex plugin provide a declarative mechanism to automatically execute custom Node.js scripts at specific lifecycle moments—SessionStart, SessionEnd, and Stop—enabling session initialization, cleanup, and optional review gates without modifying core plugin code.

The openai/codex-plugin-cc repository implements a flexible hook system that decouples session lifecycle management from the main Codex runtime. By declaring command hooks in a central JSON configuration, the plugin can spawn sandboxed processes to handle setup, teardown, and quality-control decisions, keeping the core architecture minimal while supporting extensible automation.

How Hook Files Are Defined

Hook definitions reside in plugins/codex/hooks/hooks.json, which declares three distinct hook points. Each point maps to one or more command hooks that specify the script to execute, its arguments, and execution constraints.

The configuration structure follows this pattern:

  • type: "command" – Instructs the runtime to spawn a child process.
  • command – A platform-agnostic path template using ${CLAUDE_PLUGIN_ROOT} to resolve script locations (e.g., node "${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs").
  • timeout – A hard limit in seconds (ranging from 30s for lifecycle hooks to 900s for review gates) after which the process is terminated.

This declarative approach allows new behaviors to be added by dropping a script into plugins/codex/scripts/ and updating the JSON array, without touching the main plugin source.

When Hook Files Execute

The Codex runtime triggers hooks at three critical session transitions:

SessionStart

Runs immediately when a new Codex session is created. The session-lifecycle-hook.mjs script initializes per-session tracking, validates environment variables, and records the session ID to establish context for the conversation.

SessionEnd

Fires when a user explicitly ends a session. The same session-lifecycle-hook.mjs handles cleanup, updating job records and ensuring any lingering state is properly persisted or removed.

Stop (Review Gate)

Activates when a user attempts to terminate the session via the "stop" command. The stop-review-gate-hook.mjs script implements an optional quality gate that can block termination if the Codex companion determines additional work is required.

How Hook Scripts Process Data

When the runtime executes a hook, it establishes a structured communication protocol with the Node.js script:

  1. Input via stdin – The runtime serializes session context (current working directory, last assistant message, session ID) as JSON and pipes it to the script's standard input.
  2. Execution environment – Scripts run in a sandboxed Node process with access to utility libraries in plugins/codex/scripts/lib/ (including state.mjs, job-control.mjs, and prompts.mjs).
  3. Output via stdout – Scripts must emit a JSON decision object. For example, the stop-review gate outputs { "decision": "block", "reason": "Incomplete implementation detected" } to signal the session should remain open.
  4. Logging via stderr – Human-readable notes and debug information are written to standard error, while stdout remains strictly for machine-parseable JSON responses.

In stop-review-gate-hook.mjs (lines 42-78), the main() function demonstrates this flow by reading the previous Claude response, constructing a review prompt, invoking the Codex companion task, and parsing the companion's verdict to determine whether to allow or block session termination.

Why the Hook System Exists

The hook architecture serves three strategic purposes:

  • State Initialization – Automatically configures per-session tracking variables and environment validation before the user interacts with Codex.
  • Quality Enforcement – Implements optional review gates that prevent accidental session closure when work is incomplete, reducing risk of context loss.
  • Extensibility – Maintains minimal core logic while allowing customization through external scripts. Teams can implement custom cleanup routines, audit logging, or integration triggers by adding entries to hooks.json without forking the plugin.

Practical Code Examples

Simulating a SessionStart Hook Locally

To understand how the runtime invokes hooks, you can manually pipe JSON data to the lifecycle script:


# Simulate the SessionStart hook with session context

echo '{"session_id":"12345","cwd":"/my/project"}' | \
  node plugins/codex/scripts/session-lifecycle-hook.mjs SessionStart

Adding a Custom Cleanup Hook

To extend the Stop behavior with additional logic, modify plugins/codex/hooks/hooks.json:

{
  "hooks": {
    "Stop": [
      {
        "type": "command",
        "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/custom-cleanup.mjs\"",
        "timeout": 30
      }
    ]
  }
}

Programmatic Hook Execution

While typically triggered by the runtime, hooks can be invoked programmatically following the same contract:

import { spawn } from 'child_process';
import { fileURLToPath } from 'url';

const context = {
  session_id: '12345',
  cwd: process.cwd(),
  last_assistant_message: "Here's the suggested implementation..."
};

const hook = spawn('node', [
  'plugins/codex/scripts/stop-review-gate-hook.mjs'
], {
  stdio: ['pipe', 'pipe', 'pipe']
});

// Send context via stdin
hook.stdin.write(JSON.stringify(context));
hook.stdin.end();

// Parse decision from stdout
hook.stdout.on('data', (data) => {
  const result = JSON.parse(data.toString());
  console.log(`Decision: ${result.decision}`); // "allow" or "block"
});

Key Source Files

  • plugins/codex/hooks/hooks.json – Declarative registry of hook points and associated commands.
  • plugins/codex/scripts/session-lifecycle-hook.mjs – Handles SessionStart and SessionEnd events for setup and teardown.
  • plugins/codex/scripts/stop-review-gate-hook.mjs – Implements the stop-time review gate logic (lines 42-78 contain the main decision flow).
  • plugins/codex/scripts/lib/ – Utility modules (state.mjs, job-control.mjs, prompts.mjs) supporting hook script operations.

Summary

  • Hook files provide a declarative, JSON-driven mechanism to execute Node.js scripts at specific Codex session lifecycle points.
  • The system supports three primary hooks: SessionStart (initialization), SessionEnd (cleanup), and Stop (review gate).
  • Scripts communicate via stdin/stdout using JSON payloads, with stderr reserved for logging.
  • Timeout constraints prevent runaway processes, with limits ranging from 30 to 900 seconds depending on the hook type.
  • The architecture enables extensible automation without modifying the core plugin codebase, supporting custom workflows through simple configuration updates.

Frequently Asked Questions

How do I add a new hook to the Codex plugin?

Create a new Node.js script in plugins/codex/scripts/, then add a command entry to plugins/codex/hooks/hooks.json under the appropriate hook point (SessionStart, SessionEnd, or Stop). Specify the script path using the ${CLAUDE_PLUGIN_ROOT} variable, set a timeout value, and ensure your script reads from stdin and writes a JSON response to stdout.

What happens if a hook script exceeds its timeout?

The Codex runtime forcefully terminates the child process if execution exceeds the seconds specified in the timeout field. For lifecycle hooks, this is typically 30 seconds, while the stop-review gate allows up to 900 seconds for complex companion-based evaluations. The runtime treats a timeout as a failure and logs the event to stderr.

Can hook scripts block session termination?

Yes, specifically the Stop hook point. The stop-review-gate-hook.mjs script can emit a JSON response with "decision": "block" to prevent the session from closing. This quality gate allows the Codex companion to review the conversation state and determine whether critical tasks remain incomplete before allowing the user to exit.

Why does the hook system use stdin/stdout instead of function calls?

The stdin/stdout protocol creates a sandboxed boundary between the core Codex runtime and custom hook logic. This separation prevents plugin scripts from crashing the main application, allows timeouts to be enforced cleanly, and enables hooks to be written in any language (though the current implementation uses Node.js) as long as they adhere to the JSON communication contract.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →