Codex Companion Lifecycle Hooks Explained: When SessionStart, SessionEnd, and Stop Fire
Codex Companion registers three optional lifecycle hooks—SessionStart, SessionEnd, and Stop—in plugins/codex/hooks/hooks.json that fire on session creation, session termination, and explicit user cancellation respectively.
The openai/codex-plugin-cc repository implements a robust hook system for managing Codex session lifecycles. Understanding when these hooks fire and what they execute is essential for customizing behavior or debugging session management issues.
Hook Registration in hooks.json
All three hooks are defined in plugins/codex/hooks/hooks.json. Each entry specifies the Node.js command to run and a timeout value that prevents indefinite hanging.
// plugins/codex/hooks/hooks.json (excerpt)
{
"SessionStart": {
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs\" SessionStart",
"timeout": 5000
},
"SessionEnd": {
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs\" SessionEnd",
"timeout": 5000
},
"Stop": {
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop-review-gate-hook.mjs\"",
"timeout": 900000
}
}
These hooks are optional—the platform only executes them if the entries exist in hooks.json. By default, all three are configured and active.
SessionStart Hook: Fires Immediately After Session Creation
The SessionStart hook triggers immediately after a new Codex session initializes.
What It Does
In plugins/codex/scripts/session-lifecycle-hook.mjs, the handleSessionStart function performs essential setup:
// session-lifecycle-hook.mjs lines 20-30
async function handleSessionStart(sessionId, transcriptPath, pluginData) {
await appendEnvVar('CODEX_SESSION_ID', sessionId);
await appendEnvVar('CODEX_TRANSCRIPT_PATH', transcriptPath);
await appendEnvVar('CODEX_PLUGIN_DATA', JSON.stringify(pluginData));
}
This stores three critical values in the environment:
CODEX_SESSION_ID– unique identifier for the sessionCODEX_TRANSCRIPT_PATH– location of the conversation transcriptCODEX_PLUGIN_DATA– serialized plugin configuration
The 5-second timeout ensures rapid failure if environment setup hangs.
SessionEnd Hook: Fires When Sessions Terminate
The SessionEnd hook executes whenever a Codex session ends—whether through normal completion or user cancellation.
What It Does
The handleSessionEnd function in session-lifecycle-hook.mjs (lines 83-114) orchestrates comprehensive cleanup:
| Cleanup Action | Function Called |
|---|---|
| Shut down broker endpoint | sendBrokerShutdown() |
| Remove queued and running jobs | cleanupSessionJobs() |
| Tear down broker session | teardownBrokerSession() |
// Example: Manual SessionEnd invocation (matches platform behavior)
const { execSync } = require('child_process');
execSync('node plugins/codex/scripts/session-lifecycle-hook.mjs SessionEnd', {
env: { CLAUDE_PLUGIN_ROOT: process.cwd() },
timeout: 5000,
});
This ensures no orphaned processes or dangling job queues persist after a session closes.
Stop Hook: Fires on Explicit User Cancellation
The Stop hook differs from SessionEnd—it specifically handles the review-gate stop command rather than general session termination.
When It Fires
The Stop hook triggers when a user explicitly clicks "Stop" in the UI to abort a running review-gate process. This can occur independent of normal session lifecycle events.
What It Does
The stop-review-gate-hook.mjs script executes the stop-review-gate workflow:
// Example: Manual Stop hook invocation
execSync('node plugins/codex/scripts/stop-review-gate-hook.mjs', {
env: { CLAUDE_PLUGIN_ROOT: process.cwd() },
timeout: 900000, // 15 minutes
});
The dramatically longer timeout (900 seconds vs. 5 seconds) accommodates the complexity of aborting pending review checks and cleaning up associated state without leaving partially processed gates.
Timeout Values and Reliability
| Hook | Timeout | Rationale |
|---|---|---|
| SessionStart | 5 seconds | Environment setup should be near-instant |
| SessionEnd | 5 seconds | Cleanup operations are lightweight |
| Stop | 15 minutes | Review-gate abortion may require negotiating with external systems |
These timeouts guarantee that hook failures don't indefinitely block the main Codex process.
Key Implementation Files
| File Path | Purpose |
|---|---|
plugins/codex/hooks/hooks.json |
Hook definitions and command mappings |
plugins/codex/scripts/session-lifecycle-hook.mjs |
SessionStart and SessionEnd handlers |
plugins/codex/scripts/stop-review-gate-hook.mjs |
Stop hook implementation |
Summary
- SessionStart fires after session creation, storing session metadata via
handleSessionStartinsession-lifecycle-hook.mjs - SessionEnd fires on any session termination, cleaning up brokers and jobs via
handleSessionEndin the same file - Stop fires only on explicit review-gate cancellation, running
stop-review-gate-hook.mjswith a 15-minute timeout - All hooks are optional and configured in
plugins/codex/hooks/hooks.jsonwith appropriate timeouts
Frequently Asked Questions
What happens if a hook times out?
The platform aborts the hook execution and continues. The main Codex session proceeds regardless of hook success or failure, ensuring reliability isn't compromised by plugin issues.
Can I disable specific hooks?
Yes. Remove the corresponding entry from plugins/codex/hooks/hooks.json. The platform checks for hook existence before execution, so missing entries are silently skipped.
How does SessionEnd differ from Stop?
SessionEnd runs for every session closure—normal or abnormal—and performs general resource cleanup. Stop only runs when the user explicitly cancels a review-gate process and handles aborting that specific workflow.
Where does CLAUDE_PLUGIN_ROOT come from?
The environment variable is set by the hosting platform to the absolute path of the codex-plugin-cc installation. Hook commands use it to locate scripts reliably regardless of working directory.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →