neondatabase/neon · error
Endpoint::from_dir_entry failed: '{}' is not a directory
Error message
Endpoint::from_dir_entry failed: '{}' is not a directory What it means
Endpoint::from_dir_entry is called for each entry under the endpoints root directory when listing/loading endpoints; each entry is expected to be a directory named with the endpoint id and containing endpoint.json. If an entry is a regular file (or symlink to one), loading aborts with this error before any parsing happens.
Source
Thrown at control_plane/src/endpoint.rs:408
pub struct EndpointStartArgs {
pub auth_token: Option<String>,
pub endpoint_storage_token: String,
pub endpoint_storage_addr: String,
pub safekeepers_generation: Option<SafekeeperGeneration>,
pub safekeepers: Vec<NodeId>,
pub pageserver_conninfo: PageserverConnectionInfo,
pub remote_ext_base_url: Option<String>,
pub create_test_user: bool,
pub start_timeout: Duration,
pub autoprewarm: bool,
pub offload_lfc_interval_seconds: Option<std::num::NonZeroU64>,
pub dev: bool,
}
impl Endpoint {
fn from_dir_entry(entry: std::fs::DirEntry, env: &LocalEnv) -> Result<Endpoint> {
if !entry.file_type()?.is_dir() {
anyhow::bail!(
"Endpoint::from_dir_entry failed: '{}' is not a directory",
entry.path().display()
);
}
// parse data directory name
let fname = entry.file_name();
let endpoint_id = fname.to_str().unwrap().to_string();
// Read the endpoint.json file
let conf: EndpointConf =
serde_json::from_slice(&std::fs::read(entry.path().join("endpoint.json"))?)?;
debug!("serialized endpoint conf: {:?}", conf);
Ok(Endpoint {
pg_address: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), conf.pg_port),
external_http_address: SocketAddr::new(View on GitHub (pinned to 8f60b04da4)
Solutions
- List the endpoints directory (`ls -la <neon_dir>/endpoints`) and delete or move any non-directory entries.
- Recreate the endpoint properly if a partial create left junk: remove the stray file and run endpoint create again.
- Avoid writing anything into the neon_local endpoints root; put scratch files elsewhere.
- Filter non-directory entries before calling the listing API if you build tooling on top of it.
Example fix
// before
for entry in std::fs::read_dir(endpoints_dir)? {
let ep = Endpoint::from_dir_entry(entry?, &env)?; // bails on stray file
}
// after
for entry in std::fs::read_dir(endpoints_dir)? {
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue; // skip stray files instead of failing the whole listing
}
let ep = Endpoint::from_dir_entry(entry, &env)?;
} Defensive patterns
Strategy: validation
Validate before calling
// only pass directory entries to the listing path
for entry in std::fs::read_dir(&endpoints_dir)? {
let entry = entry?;
if !entry.file_type()?.is_dir() { continue; }
let ep = Endpoint::from_dir_entry(entry, &env)?;
} Type guard
fn is_endpoint_dir(entry: &std::fs::DirEntry) -> bool {
entry.file_type().map(|t| t.is_dir()).unwrap_or(false)
} Try / catch
match Endpoint::from_dir_entry(entry, &env) {
Err(e) if e.to_string().contains("is not a directory") => continue, // skip stray file
other => other?,
} Prevention
- Keep the neon_local endpoints root pristine; never write scratch files there.
- Validate env directories after syncing or restoring from backups.
- Add a lint in dev tooling that flags non-directory entries in the endpoints root.
When it happens
Trigger: Running an operation that enumerates endpoints (`neon_local endpoints list`, env start) when the endpoints directory contains a stray file: a manually created note, an editor backup, .DS_Store, a log file redirected into the wrong place, or a half-written directory replaced by a file.
Common situations: Developers touching the neonLocalPath endpoints dir by hand; tooling writing marker files next to endpoint dirs; a crashed `endpoint create` leaving a file instead of a directory; syncing artifacts into the repo's working dir.
Related errors
- `datadir` must be a directory when calling this function: {d
- pg_ctl failed, exit code: {}, stdout: {}, stderr: {}
- safekeeper {sk_id} does not exist
- {} did not start+pass status checks within {:?} seconds
- Failed to send signal to {process_name} with pid {pid}: {e}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/448c7ca6e6c244f2.
Report an issue: GitHub.