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

  1. Use the fallible try_init() (or check try_get_broker().is_none() first) and log instead of panicking when already configured.
  2. Audit startup paths so exactly one of init()/disable() runs per process.
  3. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/b4b240416bc240f2. Report an issue: GitHub.