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.

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.

Appendix: 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)