# What Happens When the Codex App-Server Exits Unexpectedly: Error Handling Deep Dive

> Discover what happens when the Codex app-server exits unexpectedly. Learn how `SpawnedCodexAppServerClient` handles errors, logs exit codes, and rejects pending RPC requests.

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

---

**When the Codex app-server process terminates unexpectedly, the `SpawnedCodexAppServerClient` immediately constructs a `ProtocolError` containing the exit code or signal and stderr output, then rejects all pending RPC requests through its centralized `handleExit` method.**

The `openai/codex-plugin-cc` repository implements a robust error propagation mechanism to ensure that sudden crashes in the Codex app-server do not leave client applications hanging. When the child process dies with a non-zero exit code or receives a termination signal, the client library surfaces detailed diagnostic information and fails all in-flight requests gracefully.

## How the Codex App-Server Monitors Process Exit

The `SpawnedCodexAppServerClient` class (a subclass of `AppServerClientBase`) spawns the app-server as a child process and attaches an `exit` event listener to monitor its lifecycle.

### The Exit Event Listener

In `plugins/codex/scripts/lib/app-server.mjs`, the constructor attaches an `exit` handler that captures the exit code, signal, and any stderr output:

```javascript
this.proc.on("exit", (code, signal) => {
  const stderr = this.stderr.trim();
  const detail =
    code === 0
      ? null
      : createProtocolError(
          `codex app-server exited unexpectedly (${signal ? `signal ${signal}` : `exit ${code}`}).${stderr ? `\n${stderr}` : ""}`
        );
  this.handleExit(detail);
});

```

*Source: [`app-server.mjs`, lines 9–19](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L9-L19)*

If the exit code is anything other than `0`, the listener constructs a `ProtocolError` with a descriptive message that includes either the termination signal (e.g., `SIGTERM`) or the numeric exit code, concatenated with any stderr content captured during the process lifetime.

### ProtocolError Construction

The error message format follows a strict template to aid debugging:

```

codex app-server exited unexpectedly (signal SIGTERM).
[stderr content here]

```

This ensures that consumers of the client library receive actionable diagnostic information when the Codex app-server exits unexpectedly, whether due to an internal crash, OOM killer, or manual termination.

## Centralized Exit Handling and RPC Cleanup

The `handleExit` method (lines 63–75 in `app-server.mjs`) serves as the central coordinator for process termination, handling cleanup regardless of whether the exit originated from the `exit` event, an `error` event, or a parsing failure.

### The handleExit Method Implementation

```javascript
handleExit(error) {
  if (this.exitResolved) return;
  this.exitResolved = true;
  this.exitError = error ?? null;

  // Reject all pending RPC promises
  for (const pending of this.pending.values()) {
    pending.reject(this.exitError ?? new Error("codex app-server connection closed."));
  }
  this.pending.clear();
  this.resolveExit(undefined);
}

```

*Source: [`app-server.mjs`, lines 63–75](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server.mjs#L63-L75)*

The method performs four critical actions:

1. **Prevents duplicate handling** – Uses `exitResolved` to ensure the cleanup logic runs only once.
2. **Preserves error state** – Stores the `ProtocolError` (or `null` for clean exits) in `this.exitError`.
3. **Rejects pending promises** – Iterates through the `this.pending` Map and rejects every outstanding RPC promise with the exit error or a generic "connection closed" message.
4. **Clears pending state** – Removes all entries from the pending map to free memory and prevent memory leaks.

### Pending Promise Rejection

The `this.pending` collection tracks all active RPC requests that have been sent but not yet resolved. When the Codex app-server exits unexpectedly, each pending promise is rejected immediately, allowing calling code to catch the failure and respond appropriately rather than waiting indefinitely.

## Handling Unexpected Termination in Your Code

When integrating with `SpawnedCodexAppServerClient`, wrap RPC calls in try-catch blocks to handle sudden crashes:

```javascript
import { CodexAppServerClient } from "./plugins/codex/scripts/lib/app-server.mjs";

async function robustOperation() {
  const client = await CodexAppServerClient.connect(process.cwd());
  
  try {
    const result = await client.request("analyzeCode", { file: "src/app.ts" });
    return result;
  } catch (err) {
    if (err.message.includes("exited unexpectedly")) {
      console.error("App-server crashed:", err.message);
      // Implement retry logic or fallback behavior here
    } else {
      console.error("RPC error:", err);
    }
  }
}

robustOperation();

```

This pattern ensures your application remains stable when the underlying Codex app-server process terminates unexpectedly, providing clear error messages for logging and debugging.

## Summary

- **Process monitoring**: The `SpawnedCodexAppServerClient` attaches an `exit` listener to the child process that constructs a detailed `ProtocolError` when non-zero exit codes or signals are detected.
- **Centralized cleanup**: The `handleExit` method in `app-server.mjs` guarantees that all pending RPC promises are rejected and resources are cleaned up, regardless of the exit cause.
- **Error propagation**: In-flight requests receive rejection reasons containing the exit code/signal and stderr output, enabling precise error handling in consumer applications.
- **Implementation location**: All logic resides in `plugins/codex/scripts/lib/app-server.mjs`, specifically within the `exit` event handler (lines 9–19) and `handleExit` method (lines 63–75).

## Frequently Asked Questions

### What error type is thrown when the Codex app-server exits unexpectedly?

The client throws a **`ProtocolError`** (created via `createProtocolError`) with a message formatted as `codex app-server exited unexpectedly (signal X)` or `codex app-server exited unexpectedly (exit X)`, followed by any stderr output captured from the process. This error type is used consistently across the `openai/codex-plugin-cc` codebase to indicate transport-level failures.

### Does the client retry failed requests automatically when the server crashes?

**No**, the `SpawnedCodexAppServerClient` does not implement automatic retry logic. When the Codex app-server exits unexpectedly, all pending requests are immediately rejected with the exit error. Your application must catch these rejections and implement retry logic if desired, or reconnect by creating a new client instance.

### How can I distinguish between a clean shutdown and an unexpected exit?

Check the `code` parameter in the error message or the `exitError` property. A **clean shutdown** produces an exit code of `0`, which results in `handleExit` being called with `null` as the error parameter. An **unexpected exit** produces a non-zero code or a signal string (like `SIGTERM` or `SIGKILL`), triggering the `ProtocolError` path with detailed diagnostic information.

### Where is the stderr output captured when the app-server crashes?

The client captures stderr continuously during the process lifetime in the `this.stderr` buffer. When the `exit` event fires with a non-zero code, the trimmed stderr content is appended to the `ProtocolError` message. This allows developers to see crash logs, stack traces, or error messages emitted by the Codex app-server before it terminated.