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
- Call theme::init exactly once at process startup, before anything renders output
- Guard the call site so initialization is idempotent (see exampleFix)
- 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
- Initialize the theme exactly once at process entry, before dispatching commands
- Wrap any re-initialization trigger (settings reload) behind a OnceLock guard at the call site
- In test harnesses that re-run CLI entry points, reuse one process-wide initialization
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
- theme::init() must be called before getting the theme
- failed to set subscriber
- symbols must always be initialized
- a committed transaction always materializes a workspace
- anchor is always present in the order at this point
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)