{"record":{"id":"3f1da793450ba389","repo":"BloopAI/vibe-kanban","slug":"failed-to-create-asset-directory-3f1da7","errorCode":null,"errorMessage":"Failed to create asset directory","messagePattern":"Failed to create asset directory","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/utils/src/assets.rs","lineNumber":15,"sourceCode":"use directories::ProjectDirs;\nuse rust_embed::RustEmbed;\n\nconst PROJECT_ROOT: &str = env!(\"CARGO_MANIFEST_DIR\");\n\npub fn asset_dir() -> std::path::PathBuf {\n    let path = if cfg!(debug_assertions) {\n        std::path::PathBuf::from(PROJECT_ROOT).join(\"../../dev_assets\")\n    } else {\n        prod_asset_dir_path()\n    };\n\n    // Ensure the directory exists\n    if !path.exists() {\n        std::fs::create_dir_all(&path).expect(\"Failed to create asset directory\");\n    }\n\n    path\n    // ✔ macOS → ~/Library/Application Support/MyApp\n    // ✔ Linux → ~/.local/share/myapp   (respects XDG_DATA_HOME)\n    // ✔ Windows → %APPDATA%\\Example\\MyApp\n}\n\npub fn prod_asset_dir_path() -> std::path::PathBuf {\n    ProjectDirs::from(\"ai\", \"bloop\", \"vibe-kanban\")\n        .expect(\"OS didn't give us a home directory\")\n        .data_dir()\n        .to_path_buf()\n}\n\npub fn config_path() -> std::path::PathBuf {\n    asset_dir().join(\"config.json\")\n}","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/utils/src/assets.rs#L1-L33","documentation":"asset_dir() panics via std::fs::create_dir_all(&path).expect(\"Failed to create asset directory\") when the application data directory cannot be created. create_dir_all returns Err on permission denial, read-only filesystems, path components that are files, or OS-level path resolution failures. Since asset_dir() is called by main, initialize_deployment, migrate_legacy_attachment_directories, config_path, profiles_path, and credentials_path, this panic aborts the entire process at startup or first config access.","triggerScenarios":"Calling asset_dir(), config_path(), profiles_path(), credentials_path(), initialize_deployment(), or migrate_legacy_attachment_directories() when std::fs::create_dir_all fails on the resolved prod/legacy asset directory — e.g. parent path is a regular file, the filesystem is read-only, or the process lacks write permission on the parent directory.","commonSituations":"Running in a Docker container with a read-only root filesystem and no writable HOME; HOME set to a directory owned by another user; $XDG_DATA_HOME pointing at a file or a non-writable path; broken (ENOENT/EACCES) mount points; disk full.","solutions":["Check that the resolved directory (XDG_DATA_HOME or ~/.local/share on Linux, %APPDATA% on Windows) exists and is writable by the process user.","Verify no component of the path is a regular file blocking directory creation (e.g. ~/.local/share exists as a file).","If running in a container/CI, mount or create a writable volume for the data directory.","Refactor asset_dir() to return Result and propagate the io::Error instead of expect()-panicking so callers can show a useful message."],"exampleFix":"// before\nif !path.exists() {\n    std::fs::create_dir_all(&path).expect(\"Failed to create asset directory\");\n}\n// after\nif !path.exists() {\n    std::fs::create_dir_all(&path)\n        .map_err(|e| anyhow::anyhow!(\"failed to create asset dir {:?}: {e}\", path))?;\n}","handlingStrategy":"validation","validationCode":"let path = std::env::var_os(\"XDG_DATA_HOME\")\n    .map(std::path::PathBuf::from)\n    .or_else(|| std::env::var_os(\"HOME\").map(|h| std::path::PathBuf::from(h).join(\".local/share\")))\n    .ok_or_else(|| anyhow::anyhow!(\"no home directory\"))?;\n// verify the parent is writable before triggering asset_dir()\nlet probe = path.join(\".write-probe\");\nstd::fs::write(&probe, b\"\").map_err(|e| anyhow::anyhow!(\"asset dir not writable: {e}\"))?;\nlet _ = std::fs::remove_file(&probe);","typeGuard":null,"tryCatchPattern":"// Rust has no try/catch; use catch_unwind around the panicking call if you cannot fix the source\nlet result = std::panic::catch_unwind(|| vibe_utils::asset_dir());\nmatch result {\n    Ok(path) => println!(\"asset dir: {:?}\", path),\n    Err(_) => eprintln!(\"failed to create asset directory; check permissions/HOME\"),\n}","preventionTips":["Set XDG_DATA_HOME or HOME explicitly in containers, CI, and service units.","Ensure no file exists where the asset directory path expects a directory.","Mount a writable volume for the data directory in read-only-root deployments.","Prefer APIs that return Result over expect() in library code you control."],"tags":["filesystem","panic","rust","io"],"backgroundTag":"create-dir-permission-denied","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}