## Why Managed app-server shutdowns use a fixed 60-second grace period. Allow users to adjust how long shutdown waits before forcing the process to exit. ## What changed - Add `shutdownGraceSeconds` to daemon settings, accepting integers from 0 through 300 and defaulting to 60. Zero requests graceful shutdown and then forces termination immediately. - Apply the setting to managed app-server stops and restarts, including updater-triggered restarts, and extend the lifecycle lock timeout to accommodate the maximum grace period. - Keep `stop` usable with unreadable or invalid settings by falling back to the default grace period. ## Testing Add tests for setting defaults and bounds, invalid-value fallback, preservation when saving remote-control settings, graceful process exit, and forced termination with zero or finite grace periods. GitOrigin-RevId: 8627b391323cf27b40df96935a82e66aa715f572
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 update
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.
Standalone-managed daemons check for updates after five minutes, then hourly by
default. Edit CODEX_HOME/app-server-daemon/settings.json to change this:
{"remoteControlEnabled": false,
"shutdownGraceSeconds": 60,
"updater": {"autoUpdateEnabled": false, "updateIntervalMinutes": 120}}
Positive minute intervals have no configured cap. daemon restart applies the
enabled state; the next updater wait reads a new interval. The preference does
not affect an explicit codex update command or daemon update.
daemon update checks the latest stable release once, even with automatic
updates disabled. It requires a Codex installer-owned latest-channel standalone
install. JSON reports updated, noUpdate, or unsupported, with installed
and running versions. The updater owns scheduled and manual installs. A manual
update restarts a running managed daemon, so active or queued work may be
interrupted.
Accepted updates continue if the CLI exits. Installer errors return nonzero.
The updater uses saved network settings; CLI -c overrides do not reach it.
For all managed app-server shutdowns, including explicit stop and restart and
updater-triggered restarts, shutdownGraceSeconds defaults to 60 and accepts
an integer from 0 through 300. Zero forces shutdown immediately after requesting
a graceful exit; the five-minute maximum bounds the wait even if a turn is still
running.
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
automatic updates are enabled, 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 or bootstrap is used with automatic updates enabled |
Managed binary and detached updater when supported | When supported, the platform's installer runs on the configured cadence. | 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, on the configured cadence. | 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 when automatic updates are enabled 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 start enables and persists remote control for
the managed daemon, overriding a saved disabled value. It starts or bootstraps
the daemon as needed. Plain codex remote-control runs a separate foreground
server and does not change daemon settings; codex remote-control stop stops
the managed daemon without clearing its saved remote-control preference.
daemon start and daemon restart use that saved preference. daemon bootstrap
sets it according to --remote-control (disabled when omitted).
stop sends a graceful termination request first, then force-terminates the
process after the configured grace window if it 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 remote-control launch settings and updater preferencesapp-server.pidfor the app-server process recordapp-server-updater.pidfor the pid-backed standalone updater loopdaemon.lockfor daemon-wide lifecycle serialization