atuinsh/atuin · warning · std::io::Error

Empty theme directory override and could not find theme else

Error message

Empty theme directory override and could not find theme elsewhere

What it means

Thrown by ThemeManager::load_theme_from_file when the theme-directory override (the theme_dir argument to ThemeManager::new, or the ATUIN_THEME_DIR environment variable) is present but an empty string. With an override set, the manager never falls back to the default config directory, so an empty value leaves nowhere to look for {name}.toml and it returns io::ErrorKind::NotFound immediately. Note that the higher-level ThemeManager::load_theme catches this and falls back to the built-in '(none)' theme with a tracing::warn, so end users see it as a log line, not a crash.

Source

Thrown at crates/atuin-client/src/theme.rs:416

            loaded_themes: HashMap::new(),
            debug: debug.unwrap_or(false),
            override_theme_dir: match theme_dir {
                Some(theme_dir) => Some(theme_dir),
                None => std::env::var("ATUIN_THEME_DIR").ok(),
            },
        }
    }

    // Try to load a theme from a `{name}.toml` file in the theme directory. If an override is set
    // for the theme dir (via ATUIN_THEME_DIR env) we should load the theme from there
    pub fn load_theme_from_file(
        &mut self,
        name: &str,
        max_depth: u8,
    ) -> Result<&Theme, Box<dyn error::Error>> {
        let mut theme_file = if let Some(p) = &self.override_theme_dir {
            if p.is_empty() {
                return Err(Box::new(Error::new(
                    ErrorKind::NotFound,
                    "Empty theme directory override and could not find theme elsewhere",
                )));
            }
            PathBuf::from(p)
        } else {
            let config_dir = atuin_common::utils::config_dir();
            let mut theme_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
                PathBuf::from(p)
            } else {
                let mut theme_file = PathBuf::new();
                theme_file.push(config_dir);
                theme_file
            };
            theme_file.push("themes");
            theme_file
        };

View on GitHub (pinned to 202f6ad98e)

Solutions

  1. Unset ATUIN_THEME_DIR entirely (`unset ATUIN_THEME_DIR`) so the manager uses the config directory themes folder
  2. Set ATUIN_THEME_DIR to an existing directory that contains a {theme-name}.toml file
  3. Pass None as the theme_dir argument instead of Some("") when constructing ThemeManager programmatically
  4. Use a built-in theme name (default, autumn, marine) which never touches the filesystem

Example fix

# before
export ATUIN_THEME_DIR=""
# after (option A: remove it)
unset ATUIN_THEME_DIR
# after (option B: point it somewhere real)
export ATUIN_THEME_DIR="$HOME/.config/atuin/themes"
Defensive patterns

Strategy: validation

Validate before calling

// Before constructing the manager, reject/normalize an empty override
let theme_dir = std::env::var("ATUIN_THEME_DIR").ok();
let theme_dir = theme_dir.filter(|s| !s.trim().is_empty()); // None => default dir
let manager = ThemeManager::new(Some(debug), theme_dir);

Try / catch

match manager.load_theme_from_file(name, 10) {
    Ok(theme) => { /* use theme */ }
    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
        // empty override or missing file: fall back to a builtin
        let theme = manager.load_theme("default", None);
    }
    Err(e) => { /* surface */ }
}

Prevention

When it happens

Trigger: Calling ThemeManager::new(None, Some("".to_string())) or running with ATUIN_THEME_DIR exported as an empty string, then requesting any theme that is not a built-in (default, (none), autumn, marine). The check at theme.rs:415 fires before any filesystem access, so the named theme file never even matters.

Common situations: A wrapper script or CI environment that exports ATUIN_THEME_DIR="" (e.g. `ENV_VAR=${VAR:-}` patterns); dotfiles managers that template the variable to blank when unset; test fixtures that deliberately pass an empty override (the crate's own tests do this via ThemeManager::new(Some(false), Some("".to_string()))).

Related errors


AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16). Data as JSON: /api/errors/8979e16f11f50306. Report an issue: GitHub.