How Codex Plugin Uses Notification Handlers to Track Thread, Turn, and Item Lifecycle Events
The openai/codex-plugin-cc repository utilizes a custom notification handler installed on the CodexAppServerClient to monitor lifecycle events through methods like thread/started, turn/started, and item/completed, processing them via applyTurnNotification in plugins/codex/scripts/lib/codex.mjs.
The openai/codex-plugin-cc project provides a framework for integrating Codex into development workflows. Understanding how notification handlers track the progression of threads, turns, and items is essential for building reliable Codex-powered applications. The plugin intercepts server-sent notifications to maintain accurate state throughout the session lifecycle.
Installing the Notification Handler During Turn Capture
The lifecycle tracking begins when captureTurn is invoked in plugins/codex/scripts/lib/codex.mjs. This function replaces the client's default notificationHandler with a custom implementation that routes events through applyTurnNotification.
Before installing the new handler, the code preserves the existing handler as previousHandler. This ensures that unrelated notifications can still propagate to other parts of the plugin after the current turn's processing completes.
Handler Installation Pattern
When a turn starts, the plugin calls client.setNotificationHandler(handler), which is defined in plugins/codex/scripts/lib/app-server.mjs (lines 76-78). After the turn completes, the original handler is restored using client.setNotificationHandler(previousHandler ?? null).
Lifecycle Events Processed by applyTurnNotification
The applyTurnNotification function (lines 92-154 in codex.mjs) processes seven distinct notification types that map to specific lifecycle stages:
thread/started(lines 92-99): Triggered when a new thread is created. The handler records the thread ID and initializes tracking state.thread/name/updated(lines 100-104): Fires when the thread's display name changes, allowing the plugin to update thread metadata.turn/started(lines 105-122): Signals the beginning of a turn, whether on the main thread or within a sub-agent. This initializes turn-specific state objects.item/started(lines 123-130): Indicates that an individual item—such as a command execution, file change, or reasoning step—has begun processing.item/completed(lines 131-138): Marks the completion of the corresponding item, enabling progress tracking and result capture.error(lines 139-141): Handles error notifications from Codex, allowing the plugin to capture failure states.turn/completed(lines 142-154): Signifies that the entire turn has finished, triggering cleanup operations and state reset.
Turn Ownership and Handler Delegation
Before processing any notification, the handler verifies that the message belongs to the current turn using the belongsToTurn predicate. If the notification relates to a different turn or external process, the handler delegates to previousHandler, ensuring that concurrent operations do not interfere with each other.
This delegation pattern allows multiple turns or plugin components to coexist without dropping notifications. The check happens at the entry point of the custom handler, maintaining clean separation of concerns.
Implementing Custom Notification Handlers
Developers can extend the plugin's notification handling by wrapping the existing handler. The notificationHandler property on the client object accepts any function that processes Codex protocol messages.
// Capture the original handler before installing custom logic
const previous = client.notificationHandler;
client.setNotificationHandler(msg => {
// Preserve plugin functionality
if (previous) previous(msg);
// Add custom tracking for item starts
if (msg.method === "item/started") {
console.log("Custom tracking - item started:", msg.params.item);
}
});
After the turn completes, restore the previous handler to prevent memory leaks and ensure proper cleanup:
client.setNotificationHandler(previous ?? null);
The captureTurn function abstracts this pattern internally, providing an onProgress callback that receives processed events without manual handler management:
await captureTurn(
client,
threadId,
() => client.request("turn/start", { threadId, input: buildTurnInput(prompt) }),
{
onProgress: (update) => console.log("Progress:", update)
}
);
Key Source Files and Architecture
The notification handling system spans several modules in the plugins/codex/scripts/lib/ directory:
plugins/codex/scripts/lib/codex.mjs: ImplementscaptureTurnandapplyTurnNotification, containing the core lifecycle event processing logic.plugins/codex/scripts/lib/app-server.mjs: DefinesAppServerClientBasewith thenotificationHandlerproperty andsetNotificationHandlermethod (lines 76-78).plugins/codex/scripts/lib/state.mjs: Maintains turn-capture state includingturnId,completedflags, andreasoningSummarythat the handler updates.plugins/codex/scripts/lib/process.mjs: ProvidesterminateProcessTree, invoked during session cleanup when turns complete.plugins/codex/scripts/session-lifecycle-hook.mjs: Demonstrates session-level event handling (SessionStart,SessionEnd) that complements the turn-level notifications.
Summary
- The notification handler in openai/codex-plugin-cc intercepts Codex server notifications to track thread, turn, and item lifecycles.
captureTurnincodex.mjsinstalls a custom handler that processes seven specific notification methods includingthread/started,turn/started, anditem/completed.- The
applyTurnNotificationfunction routes events to appropriate state updates while filtering irrelevant notifications viabelongsToTurn. setNotificationHandlerinapp-server.mjsmanages handler registration, preserving previous handlers to maintain plugin extensibility.- Developers can wrap handlers to add custom logic without disrupting the plugin's core lifecycle tracking.
Frequently Asked Questions
What specific notification methods does the Codex plugin handle?
The plugin processes seven notification methods in applyTurnNotification: thread/started, thread/name/updated, turn/started, item/started, item/completed, error, and turn/completed. Each maps to a specific lifecycle stage in the Codex session.
How does the plugin prevent notification conflicts between concurrent turns?
The handler uses a belongsToTurn check to verify that incoming notifications relate to the current turn. If a notification belongs to a different turn, the handler delegates to previousHandler, ensuring that concurrent operations receive their appropriate messages without interference.
Can I add custom logic to process Codex notifications without modifying the core plugin?
Yes. You can capture the existing client.notificationHandler, install a wrapper function that calls the original handler first, then add your custom processing. Always restore the original handler after your turn completes using client.setNotificationHandler(previous ?? null).
Where is the notification handler state stored in the client architecture?
The handler is stored as the notificationHandler property on the CodexAppServerClient instance, defined in plugins/codex/scripts/lib/app-server.mjs. The setNotificationHandler method provides the official interface for updating this property while maintaining proper state management.
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 →