Schniz/fnm · critical

choosing base strategy

Error message

choosing base strategy

What it means

`Directories::default()` builds fnm's whole directory layout by calling `etcetera::choose_base_strategy()`, which must resolve the user's home directory (HOME on Unix, USERPROFILE on Windows). If that resolution fails, the Result is unwrapped with `.expect("choosing base strategy")`, so every fnm command panics immediately at startup before any argument handling.

Source

Thrown at src/directories.rs:38

fn state_dir(basedirs: &impl BaseStrategy) -> Option<PathBuf> {
    xdg_dir("XDG_STATE_HOME").or_else(|| basedirs.state_dir())
}

fn cache_dir(basedirs: &impl BaseStrategy) -> PathBuf {
    xdg_dir("XDG_CACHE_HOME").unwrap_or_else(|| basedirs.cache_dir())
}

/// A helper struct for directories in fnm that uses XDG Base Directory Specification
/// if applicable for the platform.
#[derive(Debug, Clone)]
pub struct Directories(
    #[cfg(windows)] etcetera::base_strategy::Windows,
    #[cfg(not(windows))] etcetera::base_strategy::Xdg,
);

impl Default for Directories {
    fn default() -> Self {
        Self(etcetera::choose_base_strategy().expect("choosing base strategy"))
    }
}

impl Directories {
    pub fn strategy(&self) -> &impl BaseStrategy {
        &self.0
    }

    pub fn default_base_dir(&self) -> PathBuf {
        let strategy = self.strategy();
        let modern = strategy.data_dir().join("fnm");
        if modern.exists() {
            return modern;
        }

        let legacy = strategy.home_dir().join(".fnm");
        if legacy.exists() {
            return legacy;

View on GitHub (pinned to 86adc9676c)

Solutions

  1. Export a home explicitly: `export HOME=/root` (or for the service user) before invoking fnm.
  2. For systemd units add `Environment=HOME=/var/lib/myuser`; for cron add `HOME=/root` at the top of the crontab.
  3. In Dockerfiles set `ENV HOME=/root` (or create the user with a home via `useradd -m`).
  4. If patching fnm: replace the expect with graceful error propagation or fall back to the current directory/stdout-only mode.

Example fix

# before
env -i /usr/local/bin/fnm list   # panic: choosing base strategy

# after
env -i HOME=/tmp/fnm-home /usr/local/bin/fnm list   # works (creates dirs under HOME)
Defensive patterns

Strategy: validation

Validate before calling

fn home_env_present() -> bool {
    let key = if cfg!(windows) { "USERPROFILE" } else { "HOME" };
    std::env::var_os(key).is_some()
}
// run before any fnm invocation; otherwise export HOME=/tmp/fnm-home

Try / catch

std::panic::catch_unwind(|| run_fnm(args))
    .unwrap_or_else(|_| {
        eprintln!("fnm crashed at startup: HOME/USERPROFILE is probably unset");
        std::process::exit(78); // EX_CONFIG
    });

Prevention

When it happens

Trigger: Running any fnm command (`fnm list`, `fnm env`, ...) in a process where HOME/USERPROFILE is unset and the OS-level lookup also fails — `env -i fnm list`, a systemd unit/cron entry without `Environment=HOME=...`, a service account whose passwd entry has no usable home, or a minimal container.

Common situations: Docker images run with a scrubbed env; cron jobs and systemd timers (which start with a sparse environment); `sudo` into service users with env_reset; hardened CI runners that strip HOME; chroot/build sandboxes.

Related errors


AI-assisted analysis of Schniz/fnm@86adc9676c (2026-08-16). Data as JSON: /api/errors/94e83da49e405c13. Report an issue: GitHub.