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
The counterpart of the double-init panic: in non-test builds theme::get() reads the global OnceLock and panics unless theme::init already populated it. It fires when a code path renders styled output (TUI screens, tables, colored IDs) without the startup code having loaded the theme first — typically a new entry point that skips theme initialization or a library consumer calling but's rendering helpers without the CLI bootstrap.
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 2497b8007a)
Solutions
- Call theme::init(theme::load(path).unwrap_or_default()) — or init with a preset — at the very start of the entry point, before any rendering can happen
- Make get() degrade gracefully: replace the expect with THEME.get_or_init(Theme::default) so a missed init falls back to the default palette instead of crashing
- Audit every entry point (CLI, TUI, MCP) for the init call when adding one
- In unit tests the cfg(test) branch already defaults; keep testable render paths under cfg(test)
Example fix
// before
THEME.get().expect("theme::init() must be called before getting the theme")
// after — missing init degrades to the default palette
THEME.get_or_init(Theme::default) Defensive patterns
Strategy: validation
Validate before calling
// single bootstrap helper every entry point must call before rendering
fn bootstrap_theme(theme_path: Option<&std::path::Path>) {
let theme = theme_path.map_or_else(Theme::default, |p| theme::load(p).unwrap_or_default());
theme::init(theme); // must precede any theme::get() / styled output
} Try / catch
// last-resort recovery if a code path skipped init: catch, init a default, retry once
let theme = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(theme::get)) {
Ok(t) => t,
Err(_) => { theme::init(Theme::default()); theme::get() }
}; Prevention
- Audit every new entry point (CLI command, TUI mode, MCP server) for a theme::init call before any styled output
- Keep init unconditional in main; don't gate it on TUI-vs-plain mode since plain output also styles text
- In tests, the cfg(test) fallback covers missing init — don't replicate init-per-test
- If embedding the crates, wrap rendering behind a facade that inits the theme first
When it happens
Trigger: Invoking anything that calls theme::get() before theme::init: a newly added subcommand that prints styled output but forgets the init step; main() refactored so init is conditional (e.g. only in TUI mode) while another path renders early; embedding but crates in another app.
Common situations: Adding new commands or entry points; early error formatting that runs before init; using the crate as a library; version changes that move the init call deeper into startup.
Related errors
- theme may only be initialized once
- valid hex prefix
- object for prefix exists
- 'dot' (graphviz) must be installed on the system
- failed to create app settings
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/60a0b8e4204f88e3.
Report an issue: GitHub.