neondatabase/neon · error

pg_ctl status returned no status code

Error message

pg_ctl status returned no status code

What it means

The status check asks for pg_ctl's exit code and gets none (ExitStatus::code() returns None), which happens only when the process was terminated by a signal rather than exiting normally.

Source

Thrown at control_plane/src/storage_controller.rs:774

        let pg_data_path = self.env.base_data_dir.join("storage_controller_db");

        let pg_status_args = ["-D", &pg_data_path.to_string_lossy(), "status"];
        let status_exitcode = self.pg_ctl(pg_status_args).await;

        // pg_ctl status returns this exit code if postgres is not running: in this case it is
        // fine that stop failed.  Otherwise it is an error that stop failed.
        const PG_STATUS_NOT_RUNNING: i32 = 3;
        const PG_NO_DATA_DIR: i32 = 4;
        const PG_STATUS_RUNNING: i32 = 0;
        match status_exitcode.code() {
            Some(PG_STATUS_NOT_RUNNING) => Ok(false),
            Some(PG_NO_DATA_DIR) => Ok(false),
            Some(PG_STATUS_RUNNING) => Ok(true),
            Some(code) => Err(anyhow::anyhow!(
                "pg_ctl status returned unexpected status code: {:?}",
                code
            )),
            None => Err(anyhow::anyhow!("pg_ctl status returned no status code")),
        }
    }

    fn get_claims_for_path(path: &str) -> anyhow::Result<Option<Claims>> {
        let category = match path.find('/') {
            Some(idx) => &path[..idx],
            None => path,
        };

        match category {
            "status" | "ready" => Ok(None),
            "control" | "debug" => Ok(Some(Claims::new(None, Scope::Admin))),
            "v1" => Ok(Some(Claims::new(None, Scope::PageServerApi))),
            _ => Err(anyhow::anyhow!("Failed to determine claims for {}", path)),
        }
    }

    /// Simple HTTP request wrapper for calling into storage controller

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Check kernel logs (dmesg) for an OOM kill of pg_ctl and free memory
  2. Remove whatever sends the signal (kill scripts, reapers) and retry the stop/status operation
Defensive patterns

Strategy: fallback

Try / catch

match status.code() {
    Some(code) => classify(code),
    None => {
        // pg_ctl was signalled; fall back to postmaster.pid liveness
        check_postmaster_pid_alive(&pg_data_path)
    }
}

Prevention

When it happens

Trigger: pg_ctl is killed by a signal while running status — OOM killer, manual kill -9, or a test environment reaping process trees.

Common situations: Memory-pressured CI machines OOM-killing helper processes, parallel test teardown killing whole process groups.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/9a32f0e2a41dadae. Report an issue: GitHub.