Schniz/fnm · error

Invalid OS string

Error message

Invalid OS string

What it means

To resolve the current version, fnm canonicalizes the multishell symlink, takes its parent directory's file name (the installed version directory under `node-versions/`), and converts it to `&str` with `.expect("Invalid OS string")`. If that directory name contains bytes that are not valid UTF-8, `to_str()` returns None and `fnm current` (and any status display) panics.

Source

Thrown at src/current_version.rs:22

use crate::system_version;
use crate::version::Version;

pub fn current_version(config: &FnmConfig) -> Result<Option<Version>, Error> {
    let multishell_path = config.multishell_path().ok_or(Error::EnvNotApplied)?;

    if multishell_path.read_link().ok() == Some(system_version::path()) {
        return Ok(Some(Version::Bypassed));
    }

    if let Ok(resolved_path) = std::fs::canonicalize(multishell_path) {
        let installation_path = resolved_path
            .parent()
            .expect("multishell path can't be in the root");
        let file_name = installation_path
            .file_name()
            .expect("Can't get filename")
            .to_str()
            .expect("Invalid OS string");
        let version = Version::parse(file_name).map_err(|source| Error::VersionError {
            source,
            version: file_name.to_string(),
        })?;
        Ok(Some(version))
    } else {
        Ok(None)
    }
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("`fnm env` was not applied in this context.\nCan't find fnm's environment variables")]
    EnvNotApplied,
    #[error("Can't read the version as a valid semver")]
    VersionError {
        source: node_semver::SemverError,
        version: String,

View on GitHub (pinned to 86adc9676c)

Solutions

  1. Find offending names: `ls FNM_DIR/node-versions | iconv -f UTF-8 -t UTF-8` — the iconv error points at the bad entry.
  2. Recreate the alias properly: `fnm alias <installed-version> default` after removing the mangled directory.
  3. Reinstall the affected version: `fnm uninstall <ver> && fnm install <ver>`.
  4. Keep FNM_DIR on a native filesystem and avoid creating entries there with non-UTF-8 tooling.

Example fix

# before
mkdir $'FNM_DIR/node-versions/v20\xff.0'   # stray-byte name
fnm current   # panic: Invalid OS string

# after
rm -r $'FNM_DIR/node-versions/v20\xff.0'
fnm install 20 && fnm alias 20 default
fnm current   # v20.x.y
Defensive patterns

Strategy: validation

Validate before calling

fn versions_dir_is_utf8(base: &Path) -> bool {
    let dir = base.join("node-versions");
    std::fs::read_dir(&dir)
        .map(|rd| rd.filter_map(|e| e.ok()).all(|e| e.file_name().to_str().is_some()))
        .unwrap_or(false)
}

Type guard

fn is_utf8_os_string(s: &std::ffi::OsStr) -> bool {
    s.to_str().is_some()
}

Try / catch

match current_version(&multishell_path) {
    Ok(v) => println!("{}", v),
    Err(Error::VersionError { version, .. }) =>
        eprintln!("version dir '{version}' is malformed; recreate it via fnm install/alias"),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Running `fnm current` (or commands showing current status) when the version/alias directory the multishell points to under FNM_DIR/node-versions has a non-UTF-8 name — e.g. a manually created alias directory with stray bytes, or names mangled by a restore.

Common situations: Hand-crafted version dirs or aliases (`ln -s` to oddly named folders); FNM_DIR restored from a backup with encoding damage; filesystems mounted with names not stored as UTF-8; scripts creating alias dirs from untrusted input.

Related errors


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