{"record":{"id":"448c7ca6e6c244f2","repo":"neondatabase/neon","slug":"endpoint-from-dir-entry-failed-is-not-a-dir","errorCode":null,"errorMessage":"Endpoint::from_dir_entry failed: '{}' is not a directory","messagePattern":"Endpoint::from_dir_entry failed: '(.+?)' is not a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"control_plane/src/endpoint.rs","lineNumber":408,"sourceCode":"pub struct EndpointStartArgs {\n    pub auth_token: Option<String>,\n    pub endpoint_storage_token: String,\n    pub endpoint_storage_addr: String,\n    pub safekeepers_generation: Option<SafekeeperGeneration>,\n    pub safekeepers: Vec<NodeId>,\n    pub pageserver_conninfo: PageserverConnectionInfo,\n    pub remote_ext_base_url: Option<String>,\n    pub create_test_user: bool,\n    pub start_timeout: Duration,\n    pub autoprewarm: bool,\n    pub offload_lfc_interval_seconds: Option<std::num::NonZeroU64>,\n    pub dev: bool,\n}\n\nimpl Endpoint {\n    fn from_dir_entry(entry: std::fs::DirEntry, env: &LocalEnv) -> Result<Endpoint> {\n        if !entry.file_type()?.is_dir() {\n            anyhow::bail!(\n                \"Endpoint::from_dir_entry failed: '{}' is not a directory\",\n                entry.path().display()\n            );\n        }\n\n        // parse data directory name\n        let fname = entry.file_name();\n        let endpoint_id = fname.to_str().unwrap().to_string();\n\n        // Read the endpoint.json file\n        let conf: EndpointConf =\n            serde_json::from_slice(&std::fs::read(entry.path().join(\"endpoint.json\"))?)?;\n\n        debug!(\"serialized endpoint conf: {:?}\", conf);\n\n        Ok(Endpoint {\n            pg_address: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), conf.pg_port),\n            external_http_address: SocketAddr::new(","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/control_plane/src/endpoint.rs#L390-L426","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfor entry in std::fs::read_dir(endpoints_dir)? {\n    let ep = Endpoint::from_dir_entry(entry?, &env)?; // bails on stray file\n}\n\n// after\nfor entry in std::fs::read_dir(endpoints_dir)? {\n    let entry = entry?;\n    if !entry.file_type()?.is_dir() {\n        continue; // skip stray files instead of failing the whole listing\n    }\n    let ep = Endpoint::from_dir_entry(entry, &env)?;\n}","handlingStrategy":"validation","validationCode":"// only pass directory entries to the listing path\nfor entry in std::fs::read_dir(&endpoints_dir)? {\n    let entry = entry?;\n    if !entry.file_type()?.is_dir() { continue; }\n    let ep = Endpoint::from_dir_entry(entry, &env)?;\n}","typeGuard":"fn is_endpoint_dir(entry: &std::fs::DirEntry) -> bool {\n    entry.file_type().map(|t| t.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match Endpoint::from_dir_entry(entry, &env) {\n    Err(e) if e.to_string().contains(\"is not a directory\") => continue, // skip stray file\n    other => other?,\n}","preventionTips":["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."],"tags":["rust","neon","control-plane","endpoint","filesystem","directory-validation"],"backgroundTag":"directory-entry-validation","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}