{"record":{"id":"a1e42b98f4e36a3a","repo":"gitbutlerapp/gitbutler","slug":"src-is-not-a-directory","errorCode":null,"errorMessage":"'{src}' is not a directory","messagePattern":"'(.+?)' is not a directory","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-feedback/src/zip.rs","lineNumber":21,"sourceCode":"    io::{self, Read, Write},\n    path,\n    path::{Path, PathBuf},\n};\n\nuse anyhow::{Result, bail};\nuse walkdir::{DirEntry, WalkDir};\nuse zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions};\n\n/// Create a zip file from the *contents* of `src_dir` and write the zip file out to `dst_file`,\n/// possibly overwriting it if it exists.\npub fn create_zip_file_from_dir(\n    src_dir: impl AsRef<Path>,\n    dst_file: impl AsRef<Path>,\n) -> anyhow::Result<PathBuf> {\n    let src_dir = src_dir.as_ref();\n    let dst_file = dst_file.as_ref();\n    if !src_dir.is_dir() {\n        bail!(\"'{src}' is not a directory\", src = src_dir.display());\n    }\n\n    let file = fs::File::create(dst_file)?;\n    zip_dir(\n        &mut WalkDir::new(src_dir).into_iter().filter_map(Result::ok),\n        src_dir,\n        file,\n    )?;\n\n    Ok(dst_file.to_owned())\n}\n\n/// Create a zip file with `src` content in a single-file archive, with the file named `src_file_name`,\n/// and write the zip file out to `dst_file`, possibly overwriting it if it exists.\npub fn create_zip_file_from_content(\n    src: &str,\n    src_file_name: &str,\n    dst_file: impl AsRef<Path>,","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-feedback/src/zip.rs#L3-L39","documentation":"`create_zip_file_from_dir()` in but-feedback requires `src_dir` to be an existing directory (it walks it with WalkDir to build the zip). A simple `is_dir()` check bails with the offending path when the source is missing or is a file. Note the message formats `src_dir.display()` while the guard itself is on `src_dir` — the path shown is the input directory path.","triggerScenarios":"Calling feedback/log-zip creation with a path that doesn't exist, points to a regular file, or to a directory the process can't stat (permission-denied mount); race where the directory is deleted between composing the path and calling.","commonSituations":"Feedback bundle requested before logs directory was created; path built from a config value that's wrong (typo, wrong platform separator); containerized runs where the logs dir isn't mounted.","solutions":["Verify the path before calling: assert it exists and is a directory; print the canonical path on failure.","Create the logs directory eagerly at application startup so it always exists by feedback time.","Fix the configured path (trailing file component, wrong separator, env var empty).","If the source may legitimately be absent, skip zip creation and report 'nothing to attach' instead."],"exampleFix":"// before\nlet zip = but_feedback::zip::create_zip_file_from_dir(&maybe_wrong, &out)?;\n\n// after\nlet dir = maybe_wrong.canonicalize().with_context(|| maybe_wrong.display().to_string())?;\nanyhow::ensure!(dir.is_dir(), \"{dir:?} is not a directory\");\nlet zip = but_feedback::zip::create_zip_file_from_dir(&dir, &out)?;","handlingStrategy":"validation","validationCode":"// Rust — validate the source directory before zipping\nlet src = src_dir.as_ref();\nif !src.is_dir() {\n    anyhow::bail!(\n        \"log directory '{}' is missing; nothing to attach\",\n        src.display()\n    );\n}\nlet zip = but_feedback::zip::create_zip_file_from_dir(src, dst)?;","typeGuard":"fn is_zip_source_dir(p: &std::path::Path) -> bool {\n    p.is_dir()\n}","tryCatchPattern":null,"preventionTips":["Create the logs directory eagerly at app startup.","Canonicalize configured paths at load time and fail fast on typos.","Skip feedback attachment when the source is absent rather than propagating the error."],"tags":["filesystem","zip","feedback","path-validation"],"backgroundTag":"invalid-directory-path","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}