Files
codex/codex-rs/app-server-daemon
Owen LinandGitHub 512f8f8012 Improve remote-control daemon UX (#22562)
## Why

`codex remote-control` manages the app-server daemon with
`remote_control` enabled, but it previously only exposed an implicit
start path. Once started, there was no obvious top-level
`remote-control` command for stopping the daemon; users had to know
about the lower-level `codex app-server daemon stop` command.

The startup failure for missing managed installs was also ambiguous.
`codex remote-control` and daemon bootstrap require the standalone Codex
install under `CODEX_HOME/packages/standalone/current/codex`, but the
old error only said to install Codex first, which is unclear when
another `codex` binary is already on PATH. Now we add an explicit
instruction for how to get the standalone Codex install.

## What changed

- Converts `codex remote-control` into a command group while preserving
bare `codex remote-control` as the existing start behavior.
- Adds `codex remote-control start` as the explicit start path.
- Adds `codex remote-control stop`, which maps to app-server daemon
stop.
- Updates the shared daemon managed-install error to name the missing
standalone path, explain why that install is required, provide the
installer command, and tell users to rerun the command they just tried.

## Verification

- `cargo test -p codex-app-server-daemon`
- `cargo test -p codex-cli`
- `./target/debug/codex remote-control --help`
2026-05-13 18:04:08 -07:00
..

codex-app-server-daemon

codex-app-server-daemon is experimental and its lifecycle contract may change while the remote-management flow is still being developed.

codex-app-server-daemon backs the machine-readable codex app-server lifecycle commands used by remote clients such as the desktop and mobile apps. It is intended for Codex instances launched over SSH, including fresh developer machines that should expose app-server with remote_control enabled.

Platform support

The current daemon implementation is Unix-only. It uses pidfile-backed daemonization plus Unix process and file-locking primitives, and does not yet support Windows lifecycle management.

Commands

codex app-server daemon start
codex app-server daemon restart
codex app-server daemon enable-remote-control
codex app-server daemon disable-remote-control
codex app-server daemon stop
codex app-server daemon version
codex app-server daemon bootstrap --remote-control

On success, every command writes exactly one JSON object to stdout. Consumers should parse that JSON rather than relying on human-readable text. Lifecycle responses report the resolved backend, socket path, local CLI version, and running app-server version when applicable.

Bootstrap flow

For a new remote machine:

curl -fsSL https://chatgpt.com/codex/install.sh | sh
$HOME/.codex/packages/standalone/current/codex app-server daemon bootstrap --remote-control

bootstrap requires the standalone managed install. It records the daemon settings under CODEX_HOME/app-server-daemon/, starts app-server as a pidfile-backed detached process, and launches a detached updater loop.

Installation and update cases

The daemon assumes Codex is installed through install.sh and always launches the standalone managed binary under CODEX_HOME.

Situation What starts Does this daemon fetch new binaries? Does a running app-server eventually move to a newer binary on its own?
install.sh has run, but only start is used start uses CODEX_HOME/packages/standalone/current/codex No No. The managed path is used when starting or restarting, but no updater is installed.
install.sh has run, then bootstrap is used The pidfile backend uses CODEX_HOME/packages/standalone/current/codex Yes. Bootstrap launches a detached updater loop that runs install.sh hourly. Yes, while that updater process is alive and app-server is already running. After a successful fetch, the updater restarts app-server with the refreshed binary and only then replaces its own process image.
Some other tool updates the managed binary path The next fresh start or restart uses the updated file at that path Only if bootstrap is active, because the updater still runs install.sh on its normal cadence. Without bootstrap, no. With bootstrap, the next successful updater pass compares the managed binary contents after install.sh runs; if app-server is running and they differ from the updater's current image, it refreshes app-server first and then itself.

Standalone installs

For installs created by install.sh:

  • lifecycle commands always use the standalone managed binary path
  • bootstrap is supported
  • bootstrap starts a detached pid-backed updater loop that fetches via install.sh
  • after a successful refresh, if app-server is running and the managed binary contents changed, the updater restarts app-server with that binary first and only then replaces its own process image
  • the updater loop is not reboot-persistent; it must be started again by rerunning bootstrap after a reboot

Out-of-band updates

This daemon does not watch arbitrary executable files for replacement. If some other tool updates the managed binary path:

  • without bootstrap, a currently running app-server remains on the old executable image until an explicit restart
  • with bootstrap, the detached updater loop notices the changed managed binary on its next successful scheduled pass after running install.sh; if app-server is running, it refreshes app-server first and then refreshes itself once that replacement starts successfully

Lifecycle semantics

start is idempotent and returns after app-server is ready to answer the normal JSON-RPC initialize handshake on the Unix control socket.

restart stops any managed daemon and starts it again.

enable-remote-control and disable-remote-control persist the launch setting for future starts. If a managed app-server is already running, they restart it so the new setting takes effect immediately.

Top-level codex remote-control bootstraps with --remote-control when the updater loop is not running. Otherwise it enables remote control and starts the daemon normally.

stop sends a graceful termination request first, then sends a second termination signal after the grace window if the process is still alive.

All mutating lifecycle commands are serialized per CODEX_HOME, so a concurrent start, restart, enable-remote-control, disable-remote-control, stop, or bootstrap does not race another in-flight lifecycle operation.

State

The daemon stores its local state under CODEX_HOME/app-server-daemon/:

  • settings.json for persisted launch settings
  • app-server.pid for the app-server process record
  • app-server-updater.pid for the pid-backed standalone updater loop
  • daemon.lock for daemon-wide lifecycle serialization