{"record":{"id":"37e607a799a4213b","repo":"gitbutlerapp/gitbutler","slug":"theme-may-only-be-initialized-once","errorCode":null,"errorMessage":"theme may only be initialized once","messagePattern":"theme may only be initialized once","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/theme.rs","lineNumber":69,"sourceCode":"\nconst MONOKAI_THEME_LIGHT: &[u8] =\n    include_bytes!(\"../assets/syntax-highlighting-themes/Monokai Extended Light.tmTheme\");\n\n/// The minimum number of change ID characters displayed for a commit, so that\n/// short IDs remain visually distinctive.\npub(crate) const MIN_DISPLAYED_CHANGE_ID_CHARS: usize = 3;\n\n/// Global theme instance, initialized once at startup.\nstatic THEME: OnceLock<Theme> = OnceLock::new();\n\n/// Initialize the global theme.\n///\n/// Must be called exactly once, before any call to [`get`].\n/// Panics if called more than once.\npub 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","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/theme.rs#L51-L87","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbut::theme::init(theme); // second call panics: 'theme may only be initialized once'\n\n// after\nstatic THEME_INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();\nTHEME_INIT.get_or_init(|| but::theme::init(theme)); // idempotent at the call site","handlingStrategy":"validation","validationCode":"// Make the call idempotent at your boundary instead of calling init() blindly\nstatic THEME_INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();\nTHEME_INIT.get_or_init(|| but::theme::init(theme));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","once-lock","global-state","theme","initialization","panic"],"backgroundTag":"double-initialization","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}