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
- Inspect the path printed in the error and delete or rename the stray pageserver_* file so only real datadirs keep the prefix
- Run the env destroy command and re-run init to rebuild a consistent directory layout
- 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
- Never create files directly under the env base dir; keep logs and notes elsewhere
- After a failed init, run destroy before the next init
- Treat any manual edit of .neon as requiring a full re-init
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
- id mismatch: identity.toml:id={identity_toml_id} pageserver_
- directory not empty: {base_path:?}
- Failed to check node status: {e}
- pageserver connection information should be provided
- shard {shard_index} missing from pageserver_connection_info
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/b56d41a130f4e4dc.
Report an issue: GitHub.