sigoden/aichat · critical · Panic
No user's config directory
Error message
No user's config directory
What it means
`config_dir` resolves the application's config directory from (1) an env-var override, (2) `XDG_CONFIG_HOME`, and finally (3) the `dirs` crate's `config_dir()`, which it unwraps with `.expect("No user's config directory")`. On platforms where the OS cannot supply a user config directory (e.g. $HOME unset on Linux, missing profile on Windows), this panics with that message rather than returning an error.
Solutions
- Set `XDG_CONFIG_HOME` (or the app's config_dir env override) to a writable directory before running.
- Ensure `HOME` is set to an existing, readable directory for the process user.
- In containers/services, run as a user that exists in /etc/passwd with a home directory.
- Patch the call site to replace `.expect` with graceful fallback (e.g. `./` or temp dir) and a clear error instead of a panic.
Example fix
// before
env::set_var("HOME", ""); // in service environment
// after
// in the service unit or shell:
// Environment=HOME=/home/app (or XDG_CONFIG_HOME=/etc/app)
// or in code:
if dirs::config_dir().is_none() {
std::env::set_var("XDG_CONFIG_HOME", "/tmp");
} Defensive patterns
Strategy: validation
Validate before calling
fn config_env_ready() -> Result<(), String> {
if std::env::var_os("XDG_CONFIG_HOME").is_some()
|| std::env::var_os("HOME").map(|h| std::path::Path::new(&h).is_dir()).unwrap_or(false)
{
Ok(())
} else {
Err("set XDG_CONFIG_HOME or a valid HOME; otherwise config_dir() panics".into())
}
} Try / catch
// the failure is a panic, not an error — guard before first config access
match std::panic::catch_unwind(|| config::local_path("settings.json")) {
Ok(p) => load(p),
Err(_) => {
eprintln!("No user config directory: set XDG_CONFIG_HOME or HOME");
std::process::exit(78); // EX_CONFIG
}
} Prevention
- Always set XDG_CONFIG_HOME (or the app's config-dir env override) in services, cron, and containers.
- Ensure the runtime user has a valid HOME directory that exists.
- Run containers with a real passwd entry (useradd) rather than bare --user 1000.
- Wrap early config access in catch_unwind or patch .expect into a graceful fallback.
When it happens
Trigger: Calling `config_dir()`/`local_path()`/any config-loading entry point on a system where `dirs::config_dir()` returns None — typically Linux/Unix with `$HOME` unset or pointing to a nonexistent user, or a stripped container without `/home`.
Common situations: Running the CLI as a systemd service or cron job without HOME set; Docker containers running as a uid with no passwd entry; broken `$HOME` after user migration; Windows roaming-profile failures.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/6558e322ab27e7af.
Report an issue: GitHub.
Appendix: source
Thrown at src/config/mod.rs:295
config.setup_model()?;
config.setup_document_loaders();
config.setup_user_agent();
Ok(())
};
let ret = setup(&mut config);
if !info_flag {
ret?;
}
Ok(config)
}
pub fn config_dir() -> PathBuf {
if let Ok(v) = env::var(get_env_name("config_dir")) {
PathBuf::from(v)
} else if let Ok(v) = env::var("XDG_CONFIG_HOME") {
PathBuf::from(v).join(env!("CARGO_CRATE_NAME"))
} else {
let dir = dirs::config_dir().expect("No user's config directory");
dir.join(env!("CARGO_CRATE_NAME"))
}
}
pub fn local_path(name: &str) -> PathBuf {
Self::config_dir().join(name)
}
pub fn config_file() -> PathBuf {
match env::var(get_env_name("config_file")) {
Ok(value) => PathBuf::from(value),
Err(_) => Self::local_path(CONFIG_FILE_NAME),
}
}
pub fn roles_dir() -> PathBuf {
match env::var(get_env_name("roles_dir")) {
Ok(value) => PathBuf::from(value),View on GitHub (pinned to 82976d349a)