gitbutlerapp/gitbutler · critical
broker already configured
Error message
broker already configured
What it means
init() is a thin wrapper over try_init() that panics with 'broker already configured' when GLOBAL_ASKPASS_BROKER (a OnceLock) was already set. The crate's contract is that startup configures askpass exactly once — init() for the GUI, disable() for the CLI — so a second init, or init after disable, is a programming error that aborts the process.
Source
Thrown at crates/but-askpass/src/lib.rs:35
error::Error,
fmt,
sync::{Arc, OnceLock},
};
use but_core::ref_metadata::StackId;
use serde::Serialize;
use tokio::sync::{Mutex, oneshot};
static GLOBAL_ASKPASS_BROKER: OnceLock<Option<AskpassBroker>> = OnceLock::new();
/// Initialize the global askpass broker.
///
/// # Panics
/// This function should be called **exactly once** during startup if the custom askpass broker
/// needs to be used (currently only needed for GUI functionality). Otherwise, call [`disable`] at
/// startup instead.
pub fn init(submit_prompt: impl Fn(PromptEvent<Context>) + Send + Sync + 'static) {
try_init(submit_prompt).unwrap_or_else(|_| panic!("broker already configured"));
}
/// The askpass broker has already been explicitly initialized or disabled.
#[derive(Debug, Clone, Copy)]
pub struct BrokerAlreadyConfigured;
impl fmt::Display for BrokerAlreadyConfigured {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("broker already configured")
}
}
impl Error for BrokerAlreadyConfigured {}
/// Fallibly initialize the global askpass broker.
///
/// This is useful for runtime bindings that need to report startup errors to their host instead of
/// panicking across an FFI boundary.View on GitHub (pinned to caf1f223d3)
Solutions
- Use the fallible try_init() (or check try_get_broker().is_none() first) and log instead of panicking when already configured.
- Audit startup paths so exactly one of init()/disable() runs per process.
- In tests, run each case in its own process or branch on try_get_broker() to avoid re-initialization.
Example fix
// before
but_askpass::init(submit_prompt); // panics: broker already configured
// after
if but_askpass::try_init(submit_prompt).is_err() {
tracing::warn!("askpass broker already configured; skipping re-init");
} Defensive patterns
Strategy: validation
Validate before calling
// Rust: configure exactly once, fallibly
if but_askpass::try_get_broker().is_none() {
let _ = but_askpass::try_init(submit_prompt);
} Try / catch
// Rust: prefer the fallible API at startup boundaries (especially across FFI)
if let Err(err) = but_askpass::try_init(submit_prompt) {
tracing::warn!(%err, "askpass broker already configured; continuing with existing broker");
} Prevention
- Call exactly one of init()/disable() per process, from a single startup path.
- Prefer try_init() wherever a repeat call is plausible (FFI hosts, tests).
- In test binaries, run each broker-dependent case in its own process since the OnceLock never resets.
When it happens
Trigger: Calling but_askpass::init() twice in one process; calling init() after disable() already ran; test binaries that initialize the broker per test inside a shared process.
Common situations: A new startup path added a second init call alongside the existing one; embedding code calls both disable() (sensible for CLI) and init(); integration tests reusing the process where the OnceLock retains the first configuration.
Related errors
- broker has not been configured
- failed to create app settings
- theme::init() must be called before getting the theme
- askpass broker must be initialized
- failed to set subscriber
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/b4b240416bc240f2.
Report an issue: GitHub.