## Why Desktop and mobile Codex clients need a machine-readable way to bootstrap and manage `codex app-server` on remote machines reached over SSH. The same flow is also useful for bringing up app-server with `remote_control` enabled on a fresh developer machine and keeping that managed install current without requiring a human session. ## What changed - add the new experimental `codex-app-server-daemon` crate and wire it into `codex app-server daemon` lifecycle commands: `start`, `restart`, `stop`, `version`, and `bootstrap` - add explicit `enable-remote-control` and `disable-remote-control` commands that persist the launch setting and restart a running managed daemon so the change takes effect immediately - emit JSON success responses for daemon commands so remote callers can consume them directly - support a Unix-only pidfile-backed detached backend for lifecycle management - assume the standalone `install.sh` layout for daemon-managed binaries and always launch `CODEX_HOME/packages/standalone/current/codex` - add bootstrap support for the standalone managed install plus a detached hourly updater loop - harden lifecycle management around concurrent operations, pidfile ownership, stale state cleanup, updater ownership, managed-binary preflight, Unix-only rejection, forced shutdown after the graceful window, and updater process-group tracking/cleanup - document the experimental Unix-only support boundary plus the standalone bootstrap/update flow in `codex-rs/app-server-daemon/README.md` ## Verification - `cargo test -p codex-app-server-daemon -p codex-cli` - live pid validation on `cb4`: `bootstrap --remote-control`, `restart`, `version`, `stop` ## Follow-up - Add updater self-refresh so the long-lived `pid-update-loop` can replace its own executable image after installing a newer managed Codex binary.
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 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. After a successful fetch, it restarts a currently running app-server only when the managed binary reports a different version. |
| Some other tool updates the managed binary path | The next fresh start or restart uses the updated file at that path | No | Not automatically. The existing process keeps the old executable image until an explicit restart. |
Standalone installs
For installs created by install.sh:
- lifecycle commands always use the standalone managed binary path
bootstrapis supportedbootstrapstarts a detached pid-backed updater loop that fetches viainstall.sh, then restarts app-server if it is running on a different version- the updater loop is not reboot-persistent; it must be started again by
rerunning
bootstrapafter a reboot
Out-of-band updates
This daemon does not watch arbitrary executable files for replacement. If some other tool updates a binary that the daemon would use on its next launch:
- a currently running app-server remains on the old executable image
restartwill launch the updated binary- for bootstrapped daemons, the detached updater loop only reacts to updates it fetched itself; it does not watch arbitrary file replacement
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.
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