{"record":{"id":"5a96d570f43fa805","repo":"openai/codex","slug":"failed-to-read-start-time-for-pid-managed-app-serv","errorCode":null,"errorMessage":"failed to read start time for pid-managed app server {pid}","messagePattern":"failed to read start time for pid-managed app server (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/app-server-daemon/src/backend/pid.rs","lineNumber":708,"sourceCode":"}\n\n#[cfg(not(unix))]\nasync fn inspect_empty_pid_reservation(\n    _pid_path: &Path,\n    _lock_path: &Path,\n) -> Result<EmptyPidReservation> {\n    Ok(EmptyPidReservation::Stale)\n}\n\n#[cfg(unix)]\nasync fn read_process_start_time(pid: u32) -> Result<String> {\n    let output = Command::new(\"ps\")\n        .args([\"-p\", &pid.to_string(), \"-o\", \"lstart=\"])\n        .output()\n        .await\n        .context(\"failed to invoke ps for pid-managed app server\")?;\n    if !output.status.success() {\n        bail!(\"failed to read start time for pid-managed app server {pid}\");\n    }\n\n    let start_time = String::from_utf8(output.stdout)\n        .context(\"pid-managed app server start time was not utf-8\")?;\n    let start_time = start_time.trim();\n    if start_time.is_empty() {\n        bail!(\"pid-managed app server {pid} has no recorded start time\");\n    }\n    Ok(start_time.to_string())\n}\n\n#[cfg(all(test, unix))]\n#[path = \"pid_tests.rs\"]\nmod tests;\n","sourceCodeStart":690,"sourceCodeEnd":723,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/app-server-daemon/src/backend/pid.rs#L690-L723","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: error chain left unread, crash reason hidden\nrun(LifecycleCommand::Start).await?;\n\n// after: surface the context chain (includes 'Managed app-server stderr (...)' tail), then verify the environment\nif let Err(err) = run(LifecycleCommand::Start).await {\n    eprintln!(\"{err:#}\");\n}\n// daemon depends on this host's ps:\n// ps -p $$ -o lstart=   (must print a start date, not an error)","handlingStrategy":"retry","validationCode":"// preflight before LifecycleCommand::Start\nlet ps = std::process::Command::new(\"ps\")\n    .args([\"-p\", \"1\", \"-o\", \"lstart=\"])\n    .output()?;\nif !ps.status.success() || ps.stdout.trim().is_empty() {\n    anyhow::bail!(\"this host's ps cannot report lstart; daemon start will fail\");\n}\n// and confirm the managed binary starts:\n// <CODEX_HOME> managed codex: `codex app-server --help`","typeGuard":null,"tryCatchPattern":"if let Err(err) = run(LifecycleCommand::Start).await {\n    eprintln!(\"{err:#}\"); // context chain includes the 'Managed app-server stderr (...)' tail naming the real crash\n    // fix the cause, then retry exactly once\n    run(LifecycleCommand::Start).await?;\n}","preventionTips":["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."],"tags":["rust","codex","daemon","unix","subprocess","ps","startup-failure"],"backgroundTag":"spawned-process-exited-immediately","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}