# How the Session Lifecycle Hook Manages Broker Endpoint Sharing in the Codex Plugin

> Discover how the session lifecycle hook in the Codex plugin centralizes broker endpoint sharing by persisting connection details to a JSON state file, ensuring all Codex sessions share a single JSON-RPC broker.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-28

---

**The session lifecycle hook in `openai/codex-plugin-cc` centralizes broker endpoint management by persisting connection details to a JSON state file and falling back to environment variables, ensuring all Codex sessions within a workspace share a single JSON-RPC broker process.**

The **Codex plugin** runs a persistent broker process (the *app-server*) that exposes a JSON-RPC endpoint for tool execution. The **session lifecycle hook** (`session-lifecycle-hook.mjs`) orchestrates how multiple editor sessions discover, share, and gracefully shut down this broker. By leveraging a state-persistence pattern, the hook guarantees that concurrent sessions reuse the same endpoint while providing deterministic cleanup when the last session terminates.

## Broker Endpoint Persistence Architecture

The foundation of endpoint sharing relies on a state file that outlives individual process instances.

### Storing the Broker Session

When the broker starts, `ensureBrokerSession()` in **`plugins/codex/scripts/lib/broker-lifecycle.mjs`** generates a platform-specific endpoint via `createBrokerEndpoint()`—using `unix:` sockets on POSIX or named pipes (`pipe:\\.\pipe\...`) on Windows—and persists the session metadata to disk.

The `saveBrokerSession()` function writes a JSON file named [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) inside the plugin’s state directory:

```javascript
// broker-lifecycle.mjs
export function saveBrokerSession(cwd, session) {
  const stateDir = resolveStateDir(cwd);
  fs.mkdirSync(stateDir, { recursive: true });
  fs.writeFileSync(
    path.join(stateDir, "broker.json"),
    `${JSON.stringify(session, null, 2)}\n`,
    "utf8"
  );
}

```

This file contains the **endpoint URL**, **PID file path**, and **log file location**, making the broker discoverable by any subsequent process in the same workspace.

### Retrieving the Shared Endpoint

Any process can locate the active broker by calling `loadBrokerSession()`, which reads the same [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) file:

```javascript
// broker-lifecycle.mjs
export function loadBrokerSession(cwd) {
  const stateFile = resolveBrokerStateFile(cwd);
  if (!fs.existsSync(stateFile)) return null;
  return JSON.parse(fs.readFileSync(stateFile, "utf8"));
}

```

If the file exists, the function returns the session object; otherwise, it returns `null`, signaling that a new broker must be started.

## Session Lifecycle Hook Integration

The **`session-lifecycle-hook.mjs`** script acts as the gatekeeper for session startup and teardown, ensuring the broker endpoint is available to every Codex session.

### Endpoint Resolution Strategy

When handling a `SessionEnd` event (or any lifecycle transition), the hook resolves the broker location through a prioritized lookup:

1. **State file first**: It calls `loadBrokerSession(cwd)` to read the persisted [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json).
2. **Environment fallback**: If the state file is missing, it checks for `CODEX_COMPANION_APP_SERVER_ENDPOINT`, `CODEX_COMPANION_APP_SERVER_PID_FILE`, and `CODEX_COMPANION_APP_SERVER_LOG_FILE` environment variables. This allows parent processes to inject endpoint details without writing to disk.
3. **Null safety**: If neither source provides data, the hook proceeds with a `null` session, skipping broker-related cleanup.

```javascript
// session-lifecycle-hook.mjs
const brokerSession = loadBrokerSession(cwd) ??
  (process.env[BROKER_ENDPOINT_ENV] ? {
      endpoint: process.env[BROKER_ENDPOINT_ENV],
      pidFile: process.env[PID_FILE_ENV] ?? null,
      logFile: process.env[LOG_FILE_ENV] ?? null
    } : null);

```

### Graceful Shutdown Sequence

During `SessionEnd`, the hook performs a coordinated teardown:

- **JSON-RPC shutdown**: It sends a `broker/shutdown` request to `brokerSession.endpoint` via `sendBrokerShutdown()`.
- **Process cleanup**: It invokes `teardownBrokerSession()` to terminate the broker process and delete the PID file.
- **State removal**: It calls `clearBrokerSession()` to unlink [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) and remove socket/pipe files.
- **Job cleanup**: It executes `cleanupSessionJobs()` to terminate any dangling child processes associated with the terminating session.

## Cross-Session Endpoint Sharing Mechanism

The design ensures **single-broker-per-workspace** semantics while maintaining cross-process visibility.

### Singleton Broker Enforcement

When a new session starts, `ensureBrokerSession()` checks for an existing valid endpoint by calling `loadBrokerSession()` followed by `waitForBrokerEndpoint()`. If the endpoint is reachable, the existing session object is returned immediately, preventing duplicate broker processes. This guarantees that all active Codex sessions share the same JSON-RPC endpoint for tool execution.

### Environment Variable Propagation

To support nested process trees, the broker lifecycle exports critical paths via environment variables defined in **`plugins/codex/scripts/lib/app-server.mjs`**:

- `CODEX_COMPANION_APP_SERVER_ENDPOINT`
- `CODEX_COMPANION_APP_SERVER_PID_FILE`
- `CODEX_COMPANION_APP_SERVER_LOG_FILE`

Child processes spawned by the hook inherit these variables, enabling them to locate the broker even if the [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) file has not yet been written or has been temporarily locked.

## Implementation Example

The following script demonstrates how to invoke the lifecycle hook programmatically, ensuring the broker endpoint is shared across session boundaries:

```javascript
// my-script.mjs
import { spawn } from "node:child_process";
import path from "node:path";

// Trigger SessionStart to initialize or connect to the shared broker
spawn(
  "node",
  [
    path.resolve("plugins/codex/scripts/session-lifecycle-hook.mjs"),
    "SessionStart"
  ],
  {
    env: {
      CODEX_COMPANION_SESSION_ID: "session-12345",
      CODEX_TRANSCRIPT_PATH: "/tmp/transcript.json"
    }
  }
);

```

When the session concludes, invoking the same script with the `SessionEnd` argument triggers the hook to locate the shared endpoint via `loadBrokerSession()`, issue the shutdown command, and purge the state files.

## Summary

- The session lifecycle hook persists broker connection details to **[`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json)** via `saveBrokerSession()` in `broker-lifecycle.mjs`, enabling cross-process discovery.
- Endpoint resolution follows a fallback chain: state file first, then environment variables (`CODEX_COMPANION_APP_SERVER_ENDPOINT`), ensuring robustness in varied execution contexts.
- `ensureBrokerSession()` enforces a singleton broker per workspace by verifying endpoint reachability before spawning new processes.
- During `SessionEnd`, the hook orchestrates graceful shutdown via `sendBrokerShutdown()`, `teardownBrokerSession()`, and `cleanupSessionJobs()`.

## Frequently Asked Questions

### How does the session lifecycle hook locate the broker if the state file is deleted?

If [`broker.json`](https://github.com/openai/codex-plugin-cc/blob/main/broker.json) is missing, the hook falls back to environment variables `CODEX_COMPANION_APP_SERVER_ENDPOINT`, `CODEX_COMPANION_APP_SERVER_PID_FILE`, and `CODEX_COMPANION_APP_SERVER_LOG_FILE`. This fallback mechanism allows parent processes to pass endpoint details directly to child sessions without relying on filesystem state.

### What prevents multiple brokers from starting in the same workspace?

The `ensureBrokerSession()` function in `broker-lifecycle.mjs` calls `loadBrokerSession()` to check for an existing session file, then validates the endpoint with `waitForBrokerEndpoint()`. If the endpoint is responsive, it returns the existing session instead of spawning a new broker process, ensuring all sessions share a single JSON-RPC endpoint.

### Which files are responsible for broker endpoint generation and cleanup?

**`broker-endpoint.mjs`** generates platform-specific endpoint strings (Unix sockets or Windows named pipes), while **`broker-lifecycle.mjs`** handles persistence via `saveBrokerSession()` and cleanup via `clearBrokerSession()`. The **`session-lifecycle-hook.mjs`** script coordinates these operations during session transitions.

### How is the broker shut down when the last Codex session ends?

The hook's `handleSessionEnd` function retrieves the broker session, sends a JSON-RPC `broker/shutdown` request to the endpoint, then invokes `teardownBrokerSession()` to kill the process and delete the state file. It also runs `cleanupSessionJobs()` to terminate any remaining child processes associated with the session.