{"record":{"id":"60a0b8e4204f88e3","repo":"gitbutlerapp/gitbutler","slug":"theme-init-must-be-called-before-getting-the-th","errorCode":null,"errorMessage":"theme::init() must be called before getting the theme","messagePattern":"theme::init\\(\\) must be called before getting the theme","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/theme.rs","lineNumber":84,"sourceCode":"pub fn init(theme: Theme) {\n    THEME\n        .set(theme)\n        .expect(\"theme may only be initialized once\");\n}\n\n/// Return a reference to the global theme.\n///\n/// Panics if [`init`] has not been called yet.\npub fn get() -> &'static Theme {\n    #[cfg(test)]\n    {\n        THEME.get_or_init(Theme::default)\n    }\n    #[cfg(not(test))]\n    {\n        THEME\n            .get()\n            .expect(\"theme::init() must be called before getting the theme\")\n    }\n}\n\n/// Load a theme from a JSON file.\n///\n/// Fields that are absent in the file keep their [`Theme::default`] values.\npub fn load(path: &Path) -> anyhow::Result<Theme> {\n    let contents = std::fs::read_to_string(path)?;\n    let theme: Theme = serde_json::from_str(&contents)?;\n    Ok(theme)\n}\n\n/// Extension trait that lets us apply a [`Style`] to \"paint\" a string with raw ANSI escape codes.\n///\n/// ```ignore\n/// use crate::theme::Paint;\n/// let t = crate::theme::get();\n/// writeln!(out, \"{}\", t.local_branch.paint(&name))?;","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/theme.rs#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nTHEME.get().expect(\"theme::init() must be called before getting the theme\")\n\n// after — missing init degrades to the default palette\nTHEME.get_or_init(Theme::default)","handlingStrategy":"validation","validationCode":"// single bootstrap helper every entry point must call before rendering\nfn bootstrap_theme(theme_path: Option<&std::path::Path>) {\n    let theme = theme_path.map_or_else(Theme::default, |p| theme::load(p).unwrap_or_default());\n    theme::init(theme); // must precede any theme::get() / styled output\n}","typeGuard":null,"tryCatchPattern":"// last-resort recovery if a code path skipped init: catch, init a default, retry once\nlet theme = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(theme::get)) {\n    Ok(t) => t,\n    Err(_) => { theme::init(Theme::default()); theme::get() }\n};","preventionTips":["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"],"tags":["rust","global-state","initialization-order","theme","panic"],"backgroundTag":"missing-initialization","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}