# Installation

> How to obtain and launch the `codex-app-server` binary, default listen URLs, session source, logging env vars, and success signals for stdio and control-socket modes.

- Repository: openai/codex
- GitHub: https://github.com/openai/codex
- Human docs: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1
- Complete Markdown: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1/llms-full.txt

## Source Files

- `README.md`
- `Cargo.toml`
- `src/main.rs`
- `src/lib.rs`
- `src/transport.rs`
- `BUILD.bazel`

---

---
title: "Installation"
description: "How to obtain and launch the `codex-app-server` binary, default listen URLs, session source, logging env vars, and success signals for stdio and control-socket modes."
---

The `codex-app-server` crate ships a `codex-app-server` binary and is also reachable as `codex app-server` on the multitool CLI. Both entry points run the same JSON-RPC v2 server; the default transport is newline-delimited JSON on stdio (`stdio://`), and an optional Unix control socket listens at `$CODEX_HOME/app-server-control/app-server-control.sock` when you pass `--listen unix://`.

## Obtain the binary

| Path | Command / artifact | Notes |
| --- | --- | --- |
| Workspace build | `cargo build -p codex-app-server` (from `codex-rs/`) | Produces `codex-app-server` in the Cargo `target/` directory for the active profile. |
| Installed Codex CLI | `codex` package / release artifact | `codex app-server` calls `codex_app_server::run_main_with_transport_options` with the same transport stack as the standalone binary. |
| Direct invocation | `codex-app-server` on `PATH` | `src/main.rs` uses `arg0_dispatch_or_else` so the binary can run without the `codex` front-end. |
| Tests / CI | `codex_utils_cargo_bin::cargo_bin("codex-app-server")` | Integration tests spawn this path (see `tests/common/test_app_server.rs`). |

<Note>
The crate also builds `codex-app-server-test-notify-capture` for test harnesses; production integrations should use `codex-app-server` or `codex app-server`.
</Note>

## Launch the server

<Steps>
<Step title="Pick an entry point">

**Standalone binary** (full CLI surface on the binary itself):

```bash
codex-app-server
codex-app-server --listen stdio://
codex-app-server --listen unix://
codex-app-server --listen unix:///absolute/path/custom.sock
```

**Codex multitool** (recommended when Codex is already installed):

```bash
codex app-server
codex app-server --stdio
codex app-server --listen unix://
codex app-server --listen ws://127.0.0.1:4500
```

`--stdio` on `codex app-server` is equivalent to `--listen stdio://` and overrides the `--listen` default for that invocation.

</Step>
<Step title="Set Codex home (optional)">

The server resolves configuration and the default control-socket directory via `find_codex_home()` (typically `$CODEX_HOME`). Integration tests set `CODEX_HOME` explicitly when spawning the child process.

</Step>
<Step title="Choose transport">

See the listen URL table below. Stdio is the default for editor-style hosts that pipe stdin/stdout. Unix control socket mode is for local clients that speak WebSocket over the domain socket (HTTP Upgrade handshake).

</Step>
</Steps>

### Default and supported `--listen` URLs

<ParamField body="--listen" type="string" default="stdio://">
Transport endpoint URL. Parsed by `AppServerTransport::from_listen_url` in `codex-app-server-transport`.
</ParamField>

| URL | Transport | Behavior |
| --- | --- | --- |
| `stdio://` | Stdio (default) | One JSON-RPC message per line on stdin; responses and notifications on stdout. Logs go to stderr. |
| `unix://` | Unix control socket | Binds `$CODEX_HOME/app-server-control/app-server-control.sock` (mode `0600` on Unix). |
| `unix://PATH` | Unix control socket | Binds an absolute or cwd-relative socket path. |
| `ws://IP:PORT` | WebSocket + HTTP | Experimental; also serves `GET /readyz` and `GET /healthz`. Non-loopback binds require websocket auth flags. |
| `off` | None | No local listener; useful only with remote control enabled and a usable sqlite state DB. |

Constants in code: `DEFAULT_LISTEN_URL = "stdio://"`; control socket file `app-server-control.sock` under directory `app-server-control`.

### Session source

<ParamField body="--session-source" type="string" default="vscode">
**Standalone `codex-app-server` only.** Parsed by `SessionSource::from_startup_arg`. Drives product restrictions and session metadata.
</ParamField>

| Value | Maps to |
| --- | --- |
| `vscode` | `SessionSource::VSCode` (default) |
| `cli` | `SessionSource::Cli` |
| `exec` | `SessionSource::Exec` |
| `mcp`, `app-server`, `app_server`, `appserver` | `SessionSource::Mcp` |
| `unknown` | `SessionSource::Unknown` |
| Any other non-empty string | `SessionSource::Custom` (normalized to ASCII lowercase) |

<Warning>
`codex app-server` does not expose `--session-source`; the CLI path always passes `SessionSource::VSCode` into `run_main_with_transport_options`. Use the standalone binary when you need a different session source label.
</Warning>

Managed daemons started via `codex app-server daemon` use `--listen unix://` (and optionally `--remote-control`), not stdio.

### Other startup flags (standalone and `codex app-server`)

| Flag | Default | Purpose |
| --- | --- | --- |
| `--strict-config` | `false` | Exit on unknown `config.toml` fields instead of warning and loading defaults. |
| `--remote-control` | `false` (hidden) | Enable remote-control transport when sqlite state is available. |
| `--analytics-default-enabled` | `false` on binary; opt-in on `codex app-server` | Changes default analytics enablement for first-party hosts. |
| `--ws-auth`, token files, issuer/audience | unset | Required for non-loopback `ws://` listeners. |

Debug-only on **debug** `codex-app-server` builds: `--disable-plugin-startup-tasks-for-tests`, env `CODEX_APP_SERVER_MANAGED_CONFIG_PATH`, `CODEX_APP_SERVER_DISABLE_MANAGED_CONFIG`.

## Logging environment variables

Tracing is installed at startup in `run_main_with_transport_options`. Application JSON-RPC traffic is **not** mixed into log output on stdio: protocol bytes use stdout; logs use stderr.

| Variable | Effect |
| --- | --- |
| `RUST_LOG` | Controls `tracing` filter/verbosity via `EnvFilter::from_default_env()` (standard `tracing-subscriber` semantics). |
| `LOG_FORMAT=json` | Emits structured JSON logs to stderr (one event per line). Any other value (or unset) uses the default human-readable formatter. Matching is case-insensitive (`json`, `JSON`, etc.). |

Integration tests commonly set `RUST_LOG=warn` when spawning the server. OpenTelemetry uses service name `codex-app-server` (`OTEL_SERVICE_NAME` in code).

## Verify stdio mode

```mermaid
sequenceDiagram
    participant Client
    participant Stdin as codex-app-server stdin
    participant Stdout as codex-app-server stdout
    participant Stderr as codex-app-server stderr

    Client->>Stdin: initialize request (JSON line)
    Stdout-->>Client: initialize response (JSON line)
    Client->>Stdin: initialized notification (JSON line)
    Note over Stderr: RUST_LOG / LOG_FORMAT output only
    Client->>Stdin: further RPCs after handshake
```

<Check>
**Process health:** The server process stays alive after config load; it exits when the stdio connection closes and no connections remain (`shutdown_when_no_connections` for stdio).
</Check>

<Steps>
<Step title="Start with stdio transport">

```bash
CODEX_HOME=/path/to/codex-home RUST_LOG=info codex-app-server --listen stdio://
```

Or: `codex app-server --stdio`

</Step>
<Step title="Send initialize">

Write a single JSON object per line to stdin (the `"jsonrpc":"2.0"` header is omitted on the wire per protocol docs). Example:

<RequestExample>
```json
{"method":"initialize","id":0,"params":{"clientInfo":{"name":"my_client","title":"My Client","version":"0.1.0"}}}
```
</RequestExample>

</Step>
<Step title="Read the response">

Expect a JSON-RPC **response** line on stdout with matching `id`. Result fields include:

<ResponseField name="userAgent" type="string">
User agent the server will present upstream (derived from `clientInfo.name`).
</ResponseField>
<ResponseField name="codexHome" type="string">
Absolute path to the server’s Codex home directory.
</ResponseField>
<ResponseField name="platformFamily" type="string">
e.g. `unix` or `windows`.
</ResponseField>
<ResponseField name="platformOs" type="string">
e.g. `macos`, `linux`, `windows`.
</ResponseField>

</Step>
<Step title="Send initialized">

After the response, send the `initialized` **notification** (required before other methods). Pre-initialize RPCs receive `"Not initialized"`; duplicate `initialize` returns `"Already initialized"`.

</Step>
</Steps>

**Failure signals on stderr:** Invalid config (non-strict mode) logs `Invalid configuration; using defaults.` via `tracing::error!`. Strict config makes startup return an I/O error instead.

## Verify control-socket mode

```text
$CODEX_HOME/
  app-server-control/
    app-server-control.sock   # websocket upgrade endpoint (0600)
    app-server-startup.lock   # startup lock while unix listener initializes
```

<Steps>
<Step title="Start the listener">

```bash
codex-app-server --listen unix://
# or managed: codex app-server daemon start  # uses --listen unix:// internally
```

</Step>
<Step title="Confirm bind success">

On success, stderr includes a tracing line like `app-server control socket listening` with `socket_path=...`. If another server holds the socket, startup fails with `AddrInUse` and message `app-server control socket is already in use at ...`.

</Step>
<Step title="Connect a client">

Open a WebSocket over the Unix socket using the HTTP Upgrade handshake (same framing as TCP WebSocket clients). `codex app-server proxy` bridges stdin/stdout to the default socket or `--sock PATH`.

</Step>
<Step title="Complete the JSON-RPC handshake">

Per connection: `initialize` request → response → `initialized` notification, same as stdio.

</Step>
</Steps>

<Info>
There is no HTTP `/readyz` on the Unix socket transport. Readiness for control-socket mode is the successful bind log plus an accept that completes WebSocket upgrade.
</Info>

### WebSocket TCP mode (experimental)

When using `--listen ws://127.0.0.1:PORT`, stderr prints a banner with `ws://…`, `http://…/readyz`, and `http://…/healthz`. Use `GET /readyz` → `200 OK` once the listener accepts connections; `GET /healthz` → `200 OK` only when no `Origin` header is present (requests with `Origin` get `403`).

<Warning>
README marks websocket transport as experimental and unsupported for production. Non-loopback binds refuse to start without `--ws-auth capability-token` or `--ws-auth signed-bearer-token`.
</Warning>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First connection: `initialize`, `thread/start`, `turn/start`, and streaming notifications.
</Card>
<Card title="Protocol and transport" href="/protocol-and-transport">
JSON-RPC wire rules, transports, health probes, and overload code `-32001`.
</Card>
<Card title="Transports and proxy" href="/transports-and-proxy">
Choosing listen URLs, control socket paths, and `codex app-server proxy`.
</Card>
<Card title="CLI flags and errors" href="/cli-flags-and-errors">
Full flag list, websocket auth, and error codes.
</Card>
<Card title="Build a JSON-RPC client" href="/build-jsonrpc-client">
Framing, request ids, and `initialized` ordering for client implementers.
</Card>
</CardGroup>
