neondatabase/neon · error

pg_ctl status returned unexpected status code: {:?}

Error message

pg_ctl status returned unexpected status code: {:?}

What it means

storcon classifies pg_ctl status exit codes: 0 = running, 3 = not running, 4 = no data dir. Any other exit code (permissions errors on the data directory, usage errors, differing pg_ctl semantics) is unexpected and reported with this error.

Source

Thrown at control_plane/src/storage_controller.rs:770

        Ok(())
    }

    async fn is_postgres_running(&self) -> anyhow::Result<bool> {
        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)),

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Run pg_ctl -D <base>/storage_controller_db status by hand and read its stderr — it explains the unexpected code
  2. Fix ownership/permissions on the data dir
  3. Rebuild or reinstall the postgres distribution if pg_ctl itself is broken
Defensive patterns

Strategy: validation

Validate before calling

let code = pg_ctl(["-D", &dir, "status"]).await.code();
match code {
    Some(0) | Some(3) | Some(4) => { /* classified */ }
    Some(c) => tracing::warn!("unexpected pg_ctl status code {c}; run it manually for stderr"),
    None => tracing::warn!("pg_ctl terminated by a signal"),
}

Prevention

When it happens

Trigger: pg_ctl status exits with an out-of-set code: inaccessible or wrongly owned pgdata, an older/newer pg_ctl with different behavior, or a partially deleted data dir that pg_ctl cannot inspect.

Common situations: Running as a different user than the owner of storage_controller_db, mixing postgres versions under pg_install, partially deleted data directories.

Related errors


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