gitbutlerapp/gitbutler · error

theme::init() must be called before getting the theme

Error message

theme::init() must be called before getting the theme

What it means

theme::get() reads the global OnceLock; in non-test builds THEME.get() returns None until theme::init() has run, so get() panics with 'theme::init() must be called before getting the theme'. Under cfg(test) the code falls back to get_or_init(Theme::default), which is why tests never hit it but real binaries do.

Solutions

  1. Call but::theme::init(...) at the very top of the binary's entry point, before command dispatch
  2. Use the panic backtrace to find the first theme::get() caller and move initialization ahead of it
  3. If ordering cannot be guaranteed, mirror the test behavior with get_or_init(Theme::default) (see exampleFix)

Example fix

// before
THEME.get().expect("theme::init() must be called before getting the theme")

// after
THEME.get_or_init(Theme::default) // safe fallback; init() still wins when it runs first
Defensive patterns

Strategy: validation

Validate before calling

// In a new binary linking the but crate, initialize before any output:
fn main() {
    but::theme::init(but::theme::Theme::default()); // first statement, before dispatch
    // ... run commands ...
}

Prevention

When it happens

Trigger: Any rendering path that calls theme::get() (nearly every CLI output paints through it) executing before init() in a non-test binary: a new entry point, an example binary, a plugin that constructs but commands directly, or startup code reordered so a helper renders before initialization.

Common situations: Adding a new binary that links the but crate but skips the theme-init step; refactoring main so an early log/paint call runs before init; library consumers calling into but internals directly.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/60a0b8e4204f88e3. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/theme.rs:84

pub fn init(theme: Theme) {
    THEME
        .set(theme)
        .expect("theme may only be initialized once");
}

/// Return a reference to the global theme.
///
/// Panics if [`init`] has not been called yet.
pub fn get() -> &'static Theme {
    #[cfg(test)]
    {
        THEME.get_or_init(Theme::default)
    }
    #[cfg(not(test))]
    {
        THEME
            .get()
            .expect("theme::init() must be called before getting the theme")
    }
}

/// Load a theme from a JSON file.
///
/// Fields that are absent in the file keep their [`Theme::default`] values.
pub fn load(path: &Path) -> anyhow::Result<Theme> {
    let contents = std::fs::read_to_string(path)?;
    let theme: Theme = serde_json::from_str(&contents)?;
    Ok(theme)
}

/// Extension trait that lets us apply a [`Style`] to "paint" a string with raw ANSI escape codes.
///
/// ```ignore
/// use crate::theme::Paint;
/// let t = crate::theme::get();
/// writeln!(out, "{}", t.local_branch.paint(&name))?;

View on GitHub (pinned to caf1f223d3)