{"record":{"id":"bdcec2449c665adb","repo":"astrid-runtime/astrid","slug":"capsule-source-is-neither-a-directory-nor-a-regula","errorCode":null,"errorMessage":"capsule source is neither a directory nor a regular file: {}","messagePattern":"capsule source is neither a directory nor a regular file: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/source_digest.rs","lineNumber":27,"sourceCode":"\n/// Compute the registry archive digest for a local capsule source.\n///\n/// Directories are canonicalized using the same deterministic archive builder\n/// used by durable publication. Archive files are unpacked into a fresh\n/// temporary directory, validated for safe regular-file/directory entries,\n/// then canonicalized through that same builder. No principal store is opened\n/// and no durable state is mutated.\npub fn archive_digest_for_source(source: &Path) -> anyhow::Result<String> {\n    let archive = canonical_archive_for_source(source)?;\n    Ok(blake3::hash(&archive).to_hex().to_string())\n}\n\nfn canonical_archive_for_source(source: &Path) -> anyhow::Result<Vec<u8>> {\n    if source.is_dir() {\n        return crate::storage::canonical_capsule_archive(source);\n    }\n    if !source.is_file() {\n        bail!(\n            \"capsule source is neither a directory nor a regular file: {}\",\n            source.display()\n        );\n    }\n\n    let staging = tempfile::tempdir().context(\"create source digest staging directory\")?;\n    let file = fs::File::open(source)\n        .with_context(|| format!(\"open capsule archive {}\", source.display()))?;\n    let decoder = flate2::read::GzDecoder::new(file);\n    let mut archive = tar::Archive::new(decoder);\n    let mut names = BTreeSet::new();\n    for entry in archive.entries().context(\"read capsule archive entries\")? {\n        let mut entry = entry.context(\"read capsule archive entry\")?;\n        let path = entry.path().context(\"read capsule archive path\")?;\n        if path.is_absolute()\n            || path\n                .components()\n                .any(|component| matches!(component, std::path::Component::ParentDir))","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/source_digest.rs#L9-L45","documentation":"canonical_archive_for_source computes a registry archive digest for a local capsule source. It accepts a directory (canonicalized via canonical_capsule_archive) or a regular file treated as a gzipped tar archive. If the path is neither (missing path, symlink target that vanished, fifo, device, etc.), it bails because there is no defined way to digest that kind of source.","triggerScenarios":"Calling archive_digest_for_source with a path that does not exist, points at a broken symlink, is a fifo/socket/device node, or (on some platforms) a symlink whose target type cannot be determined via Path::is_dir/is_file.","commonSituations":"Typo'd path passed to a publish/digest command; source deleted or moved before digesting; a Unix socket or fifo mistakenly passed as the capsule source; running from a different working directory so a relative path no longer resolves.","solutions":["Verify the path exists and is a directory or regular file: `ls -la <path>` / `test -d <path> || test -f <path>`.","Fix the path (typos, correct working directory) and re-run the digest command.","If the source is an archive, ensure it is a regular .tar.gz file, not a named pipe or other special file.","If digesting a directory, pass the directory itself rather than a symlink to it."],"exampleFix":"// before\narchive_digest_for_source(Path::new(\"./capsule.tgz\")) // path is actually a fifo\n// after: ensure a real regular file\nassert!(Path::new(\"./capsule.tgz\").is_file());\narchive_digest_for_source(Path::new(\"./capsule.tgz\"))","handlingStrategy":"validation","validationCode":"fn validate_capsule_source(source: &Path) -> std::io::Result<()> {\n    let md = std::fs::metadata(source)?; // follows symlinks; errors if missing\n    if !(md.is_dir() || md.is_file()) {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidInput,\n            format!(\"capsule source must be a directory or regular file: {}\", source.display()),\n        ));\n    }\n    Ok(())\n}","typeGuard":"fn is_valid_source(source: &Path) -> bool {\n    source.is_dir() || source.is_file()\n}","tryCatchPattern":"match archive_digest_for_source(path) {\n    Err(e) if e.to_string().contains(\"neither a directory nor a regular file\") => {\n        anyhow::bail!(\"check the --source path: {} does not exist or is a special file\", path.display());\n    }\n    other => other,\n}","preventionTips":["Check `test -f`/`test -d` on the source path before running digest/publish commands","Run commands from the intended working directory when using relative paths","Never pass named pipes, sockets, or device nodes as capsule sources","Verify the file still exists after any pre-processing step that may move it"],"tags":["filesystem","path","validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}