{"record":{"id":"302bb4e84ca1f1ac","repo":"gitbutlerapp/gitbutler","slug":"askpass-broker-must-be-initialized-302bb4","errorCode":null,"errorMessage":"askpass broker must be initialized","messagePattern":"askpass broker must be initialized","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-git/src/context.rs","lineNumber":346,"sourceCode":"        .to_owned();\n    Ok((remote, short_name))\n}\n\nfn now_ms() -> u128 {\n    UNIX_EPOCH\n        .elapsed()\n        .expect(\"system time is set before the Unix epoch\")\n        .as_millis()\n}\n\nasync fn handle_git_prompt_push(\n    prompt: String,\n    askpass: Option<Option<StackId>>,\n) -> Option<String> {\n    if let Some(branch_id) = askpass {\n        tracing::info!(\"received prompt for branch push {branch_id:?}: {prompt:?}\");\n        askpass::get_broker()\n            .expect(\"askpass broker must be initialized\")\n            .submit_prompt(prompt, askpass::Context::Push { branch_id })\n            .await\n    } else {\n        tracing::warn!(\"received askpass push prompt but no broker was supplied; returning None\");\n        None\n    }\n}\n\nasync fn handle_git_prompt_fetch(prompt: String, askpass: Option<String>) -> Option<String> {\n    if let Some(action) = askpass {\n        tracing::info!(\"received prompt for fetch with action {action:?}: {prompt:?}\");\n        askpass::get_broker()\n            .expect(\"askpass broker must be initialized\")\n            .submit_prompt(prompt, askpass::Context::Fetch { action })\n            .await\n    } else {\n        tracing::warn!(\"received askpass fetch prompt but no broker was supplied; returning None\");\n        None","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-git/src/context.rs#L328-L364","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Call but_askpass::init(prompt_handler) exactly once at startup before any push/fetch, as the Tauri app does","For non-GUI hosts, pass askpass: None when invoking push so the handler logs a warning and returns None instead of panicking","Pre-check the broker state with the public but_askpass::try_get_broker() before supplying askpass contexts (see exampleFix)"],"exampleFix":"// before\nbut_askpass::disable();\npush_with_askpass_context(Some(stack_id)).await; // panics: 'askpass broker must be initialized'\n\n// after\nif but_askpass::try_get_broker() == Some(None) {\n    // broker explicitly disabled (CLI mode): don't attach askpass contexts\n    push_with_askpass_context(None).await;\n} else {\n    but_askpass::init(handler);\n    push_with_askpass_context(Some(stack_id)).await;\n}","handlingStrategy":"validation","validationCode":"// Pre-check the global broker state before attaching push askpass contexts\nmatch but_askpass::try_get_broker() {\n    Some(Some(_)) => { /* broker active: push with Some(stack_id) context */ }\n    Some(None) => { /* disabled (CLI): pass askpass: None */ }\n    None => { /* never initialized: call but_askpass::init(handler) first */ }\n}","typeGuard":"fn askpass_broker_active() -> bool {\n    but_askpass::try_get_broker() == Some(Some(/* broker placeholder */))\n    // practically: matches!(but_askpass::try_get_broker(), Some(Some(_)))\n}","tryCatchPattern":null,"preventionTips":["Call but_askpass::init() exactly once at GUI startup, before the first push (see gitbutler-tauri/src/main.rs:146)","In CLI/embedded hosts, call but_askpass::disable() AND construct push calls with askpass: None","Use the public try_get_broker() to assert the expected mode in test setups instead of assuming"],"tags":["rust","askpass","credentials","push","global-state","git","panic"],"backgroundTag":"askpass-broker-uninitialized","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}