{"record":{"id":"f7e9c5242bc838df","repo":"can1357/oh-my-pi","slug":"cannot-access-not-a-directory","errorCode":null,"errorMessage":"cannot access {}: Not a directory","messagePattern":"cannot access (.+?): Not a directory","errorType":"error_code","errorClass":"LsError","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/ls.rs","lineNumber":3683,"sourceCode":"}\n}\n\nuse colors::StyleManager;\npub use config::{Config, options};\nuse config::{Dereference, Files, Sort, options::QUOTING_STYLE};\nuse dired::DiredOutput;\npub use display::Format;\nuse display::{display_items, display_size, should_display, show_dir_name};\n\n#[derive(Error, Debug)]\nenum LsError {\n\t#[error(\"invalid line width: '{0}'\")]\n\tInvalidLineWidth(String),\n\n\t#[error(\"general io error: {0}\")]\n\tIOError(#[from] std::io::Error),\n\n\t#[error(\"{}\", match .1.kind() {\n\t\tErrorKind::NotADirectory => format!(\"cannot access {}: Not a directory\", .0.quote()),\n\t\tErrorKind::NotFound => format!(\"cannot access {}: No such file or directory\", .0.quote()),\n\t\tErrorKind::PermissionDenied => match .1.raw_os_error().unwrap_or(1) {\n\t\t\t1 => format!(\"cannot access {}: Operation not permitted\", .0.quote()),\n\t\t\t_ => if *.3 {\n\t\t\t\tformat!(\"cannot open directory {}: Permission denied\", .0.quote())\n\t\t\t} else {\n\t\t\t\tformat!(\"cannot open file {}: Permission denied\", .0.quote())\n\t\t\t},\n\t\t},\n\t\t_ => if 9 == .1.raw_os_error().unwrap_or(1) {\n\t\t\tformat!(\"cannot open directory {}: Bad file descriptor\", .0.quote())\n\t\t} else {\n\t\t\tformat!(\"unknown io error: {}, '{:?}'\", .0.quote(), .1)\n\t\t},\n\t})]\n\tIOErrorContext(PathBuf, std::io::Error, bool, bool),\n","sourceCodeStart":3665,"sourceCodeEnd":3701,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/ls.rs#L3665-L3701","documentation":"A specialized ls error mapping io::ErrorKind::NotADirectory to the GNU-style message 'cannot access <path>: Not a directory'. It fires when ls attempts to stat or traverse a path component that is a regular file but is used as a directory (e.g. a trailing path component below a file).","triggerScenarios":"`ls file.txt/subpath` where file.txt is a regular file; `ls file.txt/` with a trailing slash on a non-directory.","commonSituations":"Scripts building paths from variables where an intermediate component turns out to be a file; typos appending /subdir to a file path.","solutions":["Check each path component with metadata() before descending","Remove the trailing path components below the file","Verify the intended path — the parent should be a directory"],"exampleFix":"// before\nls(&host, &[\"notes.txt/details\"])?;\n// after\nif std::path::Path::new(\"notes.txt\").is_dir() {\n    ls(&host, &[\"notes.txt/details\"])?;\n} else {\n    ls(&host, &[\"notes.txt\"])?;\n}","handlingStrategy":"validation","validationCode":"fn ensure_dir(p: &Path) -> std::io::Result<()> {\n    let md = std::fs::metadata(p)?;\n    if !md.is_dir() {\n        return Err(std::io::Error::new(std::io::ErrorKind::NotADirectory, format!(\"{}: not a directory\", p.display())));\n    }\n    Ok( )\n}","typeGuard":"fn is_dir_path(p: &Path) -> bool {\n    std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match ls(&host, &[path]) {\n    Err(e) if e.to_string().contains(\"Not a directory\") => {\n        eprintln!(\"{path} is a file; listing the file itself instead\");\n        ls(&host, &[path.rsplit_once('/').map(|(p, _)| p).unwrap_or(\".\")])?;\n    }\n    other => other?,\n}","preventionTips":["Verify intermediate path components are directories before appending subpaths","Avoid trailing slashes on file paths","Build paths from validated directory handles rather than string concatenation"],"tags":["filesystem","ls","path","not-a-directory"],"backgroundTag":"not-a-directory","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}