openai/codex · error · anyhow::Error
failed to read start time for pid-managed app server {pid}
Error message
failed to read start time for pid-managed app server {pid} What it means
After spawning the managed app-server (and whenever validating a recorded pid), the backend fingerprints the process by running 'ps -p <pid> -o lstart='; a non-zero exit from ps produces this bail. Most often the freshly spawned child already exited (broken codex binary, immediate crash, unwritable CODEX_HOME, occupied control socket), so ps reports no such process; a ps that lacks lstart support (busybox) also exits non-zero. In the start path (pid.rs:200) the daemon then kills the child, removes the pid file, and appends the child's stderr log tail to the error context.
Source
Thrown at codex-rs/app-server-daemon/src/backend/pid.rs:708
}
#[cfg(not(unix))]
async fn inspect_empty_pid_reservation(
_pid_path: &Path,
_lock_path: &Path,
) -> Result<EmptyPidReservation> {
Ok(EmptyPidReservation::Stale)
}
#[cfg(unix)]
async fn read_process_start_time(pid: u32) -> Result<String> {
let output = Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "lstart="])
.output()
.await
.context("failed to invoke ps for pid-managed app server")?;
if !output.status.success() {
bail!("failed to read start time for pid-managed app server {pid}");
}
let start_time = String::from_utf8(output.stdout)
.context("pid-managed app server start time was not utf-8")?;
let start_time = start_time.trim();
if start_time.is_empty() {
bail!("pid-managed app server {pid} has no recorded start time");
}
Ok(start_time.to_string())
}
#[cfg(all(test, unix))]
#[path = "pid_tests.rs"]
mod tests;
View on GitHub (pinned to 339751715c)
Solutions
- Print the error with the full anyhow chain ({err:#}) — the daemon appends the managed server's stderr tail (<pid file>.stderr.log, last 4KB), which usually names the real crash.
- Reproduce the spawn manually with the exact argv from command_args (pid.rs:413): '<codex_bin> app-server --listen unix://' — and fix whatever makes it exit.
- Ensure a full procps ps is installed: 'ps -p 1 -o lstart=' must print a date (busybox ps lacks lstart=).
- Remove a stale or foreign control socket, or stop the conflicting instance, then retry start.
- Retry the lifecycle start after cleanup — the failed child and its pid file are already removed by the error path.
Example fix
// before: error chain left unread, crash reason hidden
run(LifecycleCommand::Start).await?;
// after: surface the context chain (includes 'Managed app-server stderr (...)' tail), then verify the environment
if let Err(err) = run(LifecycleCommand::Start).await {
eprintln!("{err:#}");
}
// daemon depends on this host's ps:
// ps -p $$ -o lstart= (must print a start date, not an error) Defensive patterns
Strategy: retry
Validate before calling
// preflight before LifecycleCommand::Start
let ps = std::process::Command::new("ps")
.args(["-p", "1", "-o", "lstart="])
.output()?;
if !ps.status.success() || ps.stdout.trim().is_empty() {
anyhow::bail!("this host's ps cannot report lstart; daemon start will fail");
}
// and confirm the managed binary starts:
// <CODEX_HOME> managed codex: `codex app-server --help` Try / catch
if let Err(err) = run(LifecycleCommand::Start).await {
eprintln!("{err:#}"); // context chain includes the 'Managed app-server stderr (...)' tail naming the real crash
// fix the cause, then retry exactly once
run(LifecycleCommand::Start).await?;
} Prevention
- Keep the managed codex binary intact — never hand-edit files under the daemon's state/bin dirs.
- Use full procps images (not busybox) wherever the daemon runs.
- Watch <pid file>.stderr.log after failed starts; it is ground truth for child crashes.
- Don't share the control socket path between instances.
When it happens
Trigger: start() at pid.rs:200 invokes ps immediately after spawn — if the child exits first, ps exits 1 and this fires; the error path cleans up and attaches the stderr tail. Also raised from process_matches_record (pid.rs:582) when revalidating a recorded pid, tolerated there only when the process no longer exists. On slim images, busybox ps rejects '-o lstart=' with a non-zero exit for every pid.
Common situations: Corrupt or mismatched managed codex binary under CODEX_HOME after a partial update; app-server crashing at boot from bad config or a conflicting listener; Docker/CI images without full procps installed; spawn/ps races on very slow or loaded machines.
Related errors
- pid-managed app server {pid} has no recorded start time
- pid-managed app-server shutdown is unsupported on this platf
- pid-managed updater shutdown is unsupported on this platform
- app-server closed the control socket
- app-server user-agent omitted version separator
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/5a96d570f43fa805.
Report an issue: GitHub.