## Why Starting a latest-channel standalone daemon required a separate `bootstrap` to enable automatic updates. A missing updater could also leave a running server without automatic updates until `bootstrap` was run again. ## What changed - Ensure a single supported updater after managed `start`, `restart`, and restarts caused by remote-control changes. Repeated starts recover a missing updater while reusing the running server. - Report updater failures as warnings so successful server starts and restarts still succeed. - Preserve an existing updater while the latest-channel marker and managed binary may be temporarily inconsistent during installation. Stop it when the latest-channel marker is removed. - Document automatic updater startup and the limitations of detecting out-of-band binary replacements. ## Testing Add Unix tests for updater reuse and recovery, invalid updater records, marker mismatches and release pinning, unmanaged servers, and bootstrap status for managed local builds. GitOrigin-RevId: 44148a8d65709f68def2072f542494620d7e574b
codex-app-server-daemon
codex-app-server-daemonis 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 daemon supports Linux, macOS, and Windows using platform-specific process and file-locking primitives. Windows startup requires a non-elevated terminal whose host permits detached child processes.
Windows automatic attachment requires the canonical socket address to fit the
108-byte AF_UNIX limit (including its terminator). A short junction alias whose
resolved address exceeds that limit falls back to the embedded server. Use a
shorter CODEX_HOME to share the daemon; discovery does not trust a mutable alias.
Shared clients use the environment inherited when the daemon started. Opening a
new terminal or clearing variables there does not clear the running daemon's
environment; per-client environment isolation is not provided.
An invocation that sets CODEX_EXEC_SERVER_URL skips implicit daemon attachment
so its executor selection is preserved. If an implicitly discovered daemon cannot
initialize the connection, the TUI starts an embedded server instead. Explicit
--remote endpoints remain authoritative and report connection failures.
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 Linux or macOS machine:
curl -fsSL https://chatgpt.com/codex/install.sh | sh
$HOME/.codex/packages/standalone/current/codex app-server daemon bootstrap --remote-control
On Windows, use a non-elevated PowerShell terminal whose host allows breakaway:
irm https://chatgpt.com/codex/install.ps1 | iex
$codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' }
& "$codexHome\packages\standalone\current\bin\codex.exe" 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. It launches a detached updater loop when the
installer selected the stable latest channel and the managed binary supports
the updater command.
Installation and update cases
The daemon uses the standalone installer (install.sh on Unix, install.ps1
on Windows) and its managed binary under CODEX_HOME/packages/standalone/current:
bin/codex or bin/codex.exe, falling back to the legacy flat layout when present.
| Situation | What starts | Does this daemon fetch new binaries? | Does a running app-server eventually move to a newer binary on its own? |
|---|---|---|---|
Latest-channel installer has run; start is used |
Managed binary and detached updater when supported | When supported, the platform's installer runs hourly. | When supported, the running server restarts with the new binary before the updater replaces itself. |
Latest-channel installer has run; bootstrap is used |
Managed binary and detached updater when supported | When supported, the platform's installer runs hourly. | When supported, the running server restarts with the new binary before the updater replaces itself. |
Installer selected an explicit release; bootstrap is used |
Managed binary only | No; the selected release stays pinned. | No; an explicit restart uses the selected binary. |
| Another tool updates the managed binary | A fresh start or explicit restart uses it; a running server is reused. | Yes, when a latest-channel updater is running. | An updater that was running through the change compares binary contents on its next successful installer pass and refreshes the server first. |
Standalone installs
For installs created by either platform's standalone installer:
- lifecycle commands always use the standalone managed binary path
bootstrapis supported- managed
start,restart, andbootstrapensure a single detached pid-backed updater loop only for a stable latest-channel release whose managed binary supports the updater command - the installer records the latest-channel selection alongside
current; selecting an explicit release clears it, even if that version is currently latest. The updater checks the selection again while holding the install lock so an in-flight update cannot override a new pin - installs made before the installer recorded channel selections need one new
latestinstallation to opt into automatic updates; until then the daemon continues to serve app-server without updating the selected release - 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; a managed start after reboot starts it again
Out-of-band updates
This daemon does not watch arbitrary executable files for replacement. If some other tool updates the managed binary path:
- an updater that was already running notices a changed managed binary on its next successful scheduled installer pass; if app-server is running, it refreshes app-server first and then refreshes itself once that replacement starts successfully
- if the updater was absent during a same-version binary replacement, a later
managed start recovers it but cannot infer the running server's previous
executable identity; use
codex app-server daemon restartto refresh the server
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.jsonfor persisted launch settingsapp-server.pidfor the app-server process recordapp-server-updater.pidfor the pid-backed standalone updater loopdaemon.lockfor daemon-wide lifecycle serialization