## Description
This PR makes `codex remote-control` behave like a foreground CLI
command by default. Running it now starts remote control, waits for
readiness, prints a clear status message with the machine name, and
stays alive until Ctrl-C.
Users who want daemon behavior can use `codex remote-control start`, and
`codex remote-control stop` now prints concise human-readable output.
`--json` remains available for scripts.
Implementation-wise, this now verifies the real app-server state instead
of just assuming startup worked. The CLI starts or connects to
app-server, probes its control socket, calls the `remoteControl/enable`
API, and waits for the remote-control status response/notification
before printing success.
For daemon mode, `codex remote-control start` also reports which managed
app-server binary was used, including its path and best-effort `codex
--version`, so failures are easier to diagnose.
## Examples
Example output:
```
> codex remote-control
Starting app-server with remote control enabled...
This machine is available for remote control as com-97826.
Press Ctrl-C to stop.
```
Error case using daemon (currently expected based on our publicly
released CLI version):
```
> ./target/debug/codex remote-control start
Starting app-server daemon with remote control enabled...
Error: app server did not become ready on /Users/owen/.codex/app-server-control/app-server-control.sock
Daemon used app-server:
path: /Users/owen/.codex/packages/standalone/current/codex
version: 0.130.0
Managed app-server stderr (/Users/owen/.codex/app-server-daemon/app-server.stderr.log):
error: unexpected argument '--remote-control' found
Usage: codex app-server [OPTIONS] [COMMAND]
For more information, try '--help'.
Caused by:
0: failed to connect to /Users/owen/.codex/app-server-control/app-server-control.sock
1: No such file or directory (os error 2)
```
## What changed
- `codex remote-control` now runs remote control in the foreground and
prints a Ctrl-C stop hint.
- `codex remote-control start` starts the daemon and waits for remote
control readiness before reporting success.
- `codex remote-control stop` reports stopped/not-running status in
plain language.
- Startup failures now include recent managed app-server stderr to make
daemon issues easier to diagnose.
- Added coverage for CLI output, readiness waiting, foreground shutdown,
and stderr log tailing.
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 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
bootstrapis supportedbootstrapstarts a detached pid-backed updater loop that fetches viainstall.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
bootstrapafter 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 explicitrestart - with
bootstrap, the detached updater loop notices the changed managed binary on its next successful scheduled pass after runninginstall.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.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