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
- 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.
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
- 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.
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
- OS didn't give us a home directory
- OS didn't give us a home directory
- Default profiles v3 JSON is invalid
- request_id called for unsupported request variant
- handled non-session commands earlier
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/3f1da793450ba389.
Report an issue: GitHub.