{"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/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/theme.rs#L66-L102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call but::theme::init(...) at the very top of the binary's entry point, before command dispatch","Use the panic backtrace to find the first theme::get() caller and move initialization ahead of it","If ordering cannot be guaranteed, mirror the test behavior with get_or_init(Theme::default) (see exampleFix)"],"exampleFix":"// before\nTHEME.get().expect(\"theme::init() must be called before getting the theme\")\n\n// after\nTHEME.get_or_init(Theme::default) // safe fallback; init() still wins when it runs first","handlingStrategy":"validation","validationCode":"// In a new binary linking the but crate, initialize before any output:\nfn main() {\n    but::theme::init(but::theme::Theme::default()); // first statement, before dispatch\n    // ... run commands ...\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call theme::init() as the first statement of every binary entry point","Remember cfg(test) masks this bug: tests default the theme, release binaries panic","When adding early diagnostics output, either skip themed painting or init the theme first"],"tags":["rust","initialization-order","theme","global-state","startup","panic"],"backgroundTag":"missing-initialization","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}