BloopAI/vibe-kanban · critical

Failed to create asset directory

Error message

Failed to create asset directory

What it means

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.

Source

Thrown at crates/utils/src/assets.rs:15

use directories::ProjectDirs;
use rust_embed::RustEmbed;

const PROJECT_ROOT: &str = env!("CARGO_MANIFEST_DIR");

pub fn asset_dir() -> std::path::PathBuf {
    let path = if cfg!(debug_assertions) {
        std::path::PathBuf::from(PROJECT_ROOT).join("../../dev_assets")
    } else {
        prod_asset_dir_path()
    };

    // Ensure the directory exists
    if !path.exists() {
        std::fs::create_dir_all(&path).expect("Failed to create asset directory");
    }

    path
    // ✔ macOS → ~/Library/Application Support/MyApp
    // ✔ Linux → ~/.local/share/myapp   (respects XDG_DATA_HOME)
    // ✔ Windows → %APPDATA%\Example\MyApp
}

pub fn prod_asset_dir_path() -> std::path::PathBuf {
    ProjectDirs::from("ai", "bloop", "vibe-kanban")
        .expect("OS didn't give us a home directory")
        .data_dir()
        .to_path_buf()
}

pub fn config_path() -> std::path::PathBuf {
    asset_dir().join("config.json")
}

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Check that the resolved directory (XDG_DATA_HOME or ~/.local/share on Linux, %APPDATA% on Windows) exists and is writable by the process user.
  2. Verify no component of the path is a regular file blocking directory creation (e.g. ~/.local/share exists as a file).
  3. If running in a container/CI, mount or create a writable volume for the data directory.
  4. Refactor asset_dir() to return Result and propagate the io::Error instead of expect()-panicking so callers can show a useful message.

Example fix

// before
if !path.exists() {
    std::fs::create_dir_all(&path).expect("Failed to create asset directory");
}
// after
if !path.exists() {
    std::fs::create_dir_all(&path)
        .map_err(|e| anyhow::anyhow!("failed to create asset dir {:?}: {e}", path))?;
}
Defensive patterns

Strategy: validation

Validate before calling

let path = std::env::var_os("XDG_DATA_HOME")
    .map(std::path::PathBuf::from)
    .or_else(|| std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join(".local/share")))
    .ok_or_else(|| anyhow::anyhow!("no home directory"))?;
// verify the parent is writable before triggering asset_dir()
let probe = path.join(".write-probe");
std::fs::write(&probe, b"").map_err(|e| anyhow::anyhow!("asset dir not writable: {e}"))?;
let _ = std::fs::remove_file(&probe);

Try / catch

// Rust has no try/catch; use catch_unwind around the panicking call if you cannot fix the source
let result = std::panic::catch_unwind(|| vibe_utils::asset_dir());
match result {
    Ok(path) => println!("asset dir: {:?}", path),
    Err(_) => eprintln!("failed to create asset directory; check permissions/HOME"),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/3f1da793450ba389. Report an issue: GitHub.