neondatabase/neon · error · anyhow::Error

expected a directory, got {:?}

Error message

expected a directory, got {:?}

What it means

Thrown by neon_local's LocalEnv while discovering pageservers from the local environment's base data directory (default .neon). It scans every entry whose name starts with 'pageserver_' and expects each to be a directory holding that pageserver's datadir; an entry of any other file type aborts env setup before the node id is even parsed.

Source

Thrown at control_plane/src/local_env.rs:719

            "we ensure this during deserialization"
        );
        env.pageservers = {
            let iter = std::fs::read_dir(repopath).context("open dir")?;
            let mut pageservers = Vec::new();
            for res in iter {
                let dentry = res?;
                const PREFIX: &str = "pageserver_";
                let dentry_name = dentry
                    .file_name()
                    .into_string()
                    .ok()
                    .with_context(|| format!("non-utf8 dentry: {:?}", dentry.path()))
                    .unwrap();
                if !dentry_name.starts_with(PREFIX) {
                    continue;
                }
                if !dentry.file_type().context("determine file type")?.is_dir() {
                    anyhow::bail!("expected a directory, got {:?}", dentry.path());
                }
                let id = dentry_name[PREFIX.len()..]
                    .parse::<NodeId>()
                    .with_context(|| format!("parse id from {:?}", dentry.path()))?;
                // TODO(christian): use pageserver_api::config::ConfigToml (PR #7656)
                #[derive(serde::Serialize, serde::Deserialize)]
                // (allow unknown fields, unlike PageServerConf)
                struct PageserverConfigTomlSubset {
                    listen_pg_addr: String,
                    listen_http_addr: String,
                    listen_https_addr: Option<String>,
                    listen_grpc_addr: Option<String>,
                    pg_auth_type: AuthType,
                    http_auth_type: AuthType,
                    grpc_auth_type: AuthType,
                    #[serde(default)]
                    no_sync: bool,
                }

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Inspect the path printed in the error and delete or rename the stray pageserver_* file so only real datadirs keep the prefix
  2. Run the env destroy command and re-run init to rebuild a consistent directory layout
  3. Ensure every pageserver datadir is a directory literally named pageserver_<NodeId> and keep notes/backups outside the base dir
Defensive patterns

Strategy: validation

Validate before calling

fn validate_pageserver_entries(base: &std::path::Path) -> anyhow::Result<()> {
    for entry in std::fs::read_dir(base)? {
        let entry = entry?;
        if entry.file_name().to_string_lossy().starts_with("pageserver_") {
            anyhow::ensure!(
                entry.file_type()?.is_dir(),
                "stray non-dir entry: {}",
                entry.path().display()
            );
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: A non-directory entry named pageserver_<anything> directly under the base data dir: a stray file (e.g. pageserver_1.bak or an editor backup), a symlink pointing to a file, or a marker file dropped by a script. read_dir yields it, file_type().is_dir() is false, and LocalEnv bails immediately.

Common situations: Manually copying or moving pageserver datadirs, partial cleanup after a crashed init/destroy, backup tools dropping files into .neon, or hand-editing the env directory instead of using the neon CLI.

Related errors


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