gitbutlerapp/gitbutler · error

askpass broker must be initialized

Error message

askpass broker must be initialized

What it means

handle_git_prompt_push is the askpass hook for pushes: when git asks for credentials and the executor carried a push context (Some(branch_id)), it calls but_askpass::get_broker(). That global returns Some only after but_askpass::init() ran (GUI startup, gitbutler-tauri/src/main.rs:146); after but_askpass::disable() (the CLI path) it returns None and expect("askpass broker must be initialized") panics. If neither init nor disable ever ran, get_broker() itself panics with 'broker has not been configured' first.

Source

Thrown at crates/gitbutler-git/src/context.rs:346

        .to_owned();
    Ok((remote, short_name))
}

fn now_ms() -> u128 {
    UNIX_EPOCH
        .elapsed()
        .expect("system time is set before the Unix epoch")
        .as_millis()
}

async fn handle_git_prompt_push(
    prompt: String,
    askpass: Option<Option<StackId>>,
) -> Option<String> {
    if let Some(branch_id) = askpass {
        tracing::info!("received prompt for branch push {branch_id:?}: {prompt:?}");
        askpass::get_broker()
            .expect("askpass broker must be initialized")
            .submit_prompt(prompt, askpass::Context::Push { branch_id })
            .await
    } else {
        tracing::warn!("received askpass push prompt but no broker was supplied; returning None");
        None
    }
}

async fn handle_git_prompt_fetch(prompt: String, askpass: Option<String>) -> Option<String> {
    if let Some(action) = askpass {
        tracing::info!("received prompt for fetch with action {action:?}: {prompt:?}");
        askpass::get_broker()
            .expect("askpass broker must be initialized")
            .submit_prompt(prompt, askpass::Context::Fetch { action })
            .await
    } else {
        tracing::warn!("received askpass fetch prompt but no broker was supplied; returning None");
        None

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Call but_askpass::init(prompt_handler) exactly once at startup before any push/fetch, as the Tauri app does
  2. For non-GUI hosts, pass askpass: None when invoking push so the handler logs a warning and returns None instead of panicking
  3. Pre-check the broker state with the public but_askpass::try_get_broker() before supplying askpass contexts (see exampleFix)

Example fix

// before
but_askpass::disable();
push_with_askpass_context(Some(stack_id)).await; // panics: 'askpass broker must be initialized'

// after
if but_askpass::try_get_broker() == Some(None) {
    // broker explicitly disabled (CLI mode): don't attach askpass contexts
    push_with_askpass_context(None).await;
} else {
    but_askpass::init(handler);
    push_with_askpass_context(Some(stack_id)).await;
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the global broker state before attaching push askpass contexts
match but_askpass::try_get_broker() {
    Some(Some(_)) => { /* broker active: push with Some(stack_id) context */ }
    Some(None) => { /* disabled (CLI): pass askpass: None */ }
    None => { /* never initialized: call but_askpass::init(handler) first */ }
}

Type guard

fn askpass_broker_active() -> bool {
    but_askpass::try_get_broker() == Some(Some(/* broker placeholder */))
    // practically: matches!(but_askpass::try_get_broker(), Some(Some(_)))
}

Prevention

When it happens

Trigger: A push that triggers a credential prompt in a process that wired the askpass push context but called but_askpass::disable() - or an embedded host/test that constructs the GixExecutor with askpass Some(..) and never calls but_askpass::init().

Common situations: Embedding gitbutler-git in another app or N-API host, test harnesses exercising push with askpass contexts, or startup-order regressions where disable() wins over GUI initialization.

Related errors


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