gitbutlerapp/gitbutler · error

theme may only be initialized once

Error message

theme may only be initialized once

What it means

theme::init() stores a Theme in a process-wide std::sync::OnceLock. OnceLock::set fails whenever the cell already holds a value, so the expect("theme may only be initialized once") fires on the second init() call in the same process. The type allows exactly one writer; the API contract is 'call exactly once at startup' (theme.rs:65-71).

Solutions

  1. Call theme::init exactly once at process startup, before anything renders output
  2. Guard the call site so initialization is idempotent (see exampleFix)
  3. For dynamic theme switching, don't re-init: propose changing the global to get_or_init/RwLock semantics, or keep one Theme and re-derive per run

Example fix

// before
but::theme::init(theme); // second call panics: 'theme may only be initialized once'

// after
static THEME_INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();
THEME_INIT.get_or_init(|| but::theme::init(theme)); // idempotent at the call site
Defensive patterns

Strategy: validation

Validate before calling

// Make the call idempotent at your boundary instead of calling init() blindly
static THEME_INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();
THEME_INIT.get_or_init(|| but::theme::init(theme));

Prevention

When it happens

Trigger: Calling but::theme::init(theme) twice in one process: a long-lived host re-invoking the CLI entry point, a test harness running the whole command pipeline repeatedly, or a new code path (e.g. theme reload after settings change) calling init again.

Common situations: Embedding the but CLI in another application, integration tests exercising full startup multiple times, or refactors that add a second initialization site without knowing startup already ran one.

Related errors


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

Appendix: source

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

const MONOKAI_THEME_LIGHT: &[u8] =
    include_bytes!("../assets/syntax-highlighting-themes/Monokai Extended Light.tmTheme");

/// The minimum number of change ID characters displayed for a commit, so that
/// short IDs remain visually distinctive.
pub(crate) const MIN_DISPLAYED_CHANGE_ID_CHARS: usize = 3;

/// Global theme instance, initialized once at startup.
static THEME: OnceLock<Theme> = OnceLock::new();

/// Initialize the global theme.
///
/// Must be called exactly once, before any call to [`get`].
/// Panics if called more than once.
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")
    }
}

View on GitHub (pinned to caf1f223d3)