{"record":{"id":"7a6e2b2dc4772a98","repo":"gitbutlerapp/gitbutler","slug":"unknown","errorCode":"Unknown","errorMessage":"git fetch worker thread panicked: {reason}","messagePattern":"git fetch worker thread panicked: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-git/src/context.rs","lineNumber":180,"sourceCode":"        )?;\n        Ok(runtime.block_on(crate::fetch(\n            repo_path,\n            crate::tokio::TokioExecutor,\n            &remote,\n            on_prompt,\n        )))\n    })\n    .join()\n    .map_err(|panic| {\n        let reason = if let Some(message) = panic.downcast_ref::<String>() {\n            message.clone()\n        } else if let Some(message) = panic.downcast_ref::<&'static str>() {\n            (*message).to_owned()\n        } else {\n            \"unknown panic payload\".to_owned()\n        };\n\n        anyhow!(\"git fetch worker thread panicked: {reason}\").context(\n            but_error::Context::new(\"git fetch failed unexpectedly\").with_code(Code::Unknown),\n        )\n    })??;\n    result.map_err(map_needs_authorization)\n}\n\n/// The concrete error type produced by fetch/push through the tokio executor.\ntype GitError = crate::Error<crate::repository::Error<crate::tokio::TokioExecutor>>;\n\n/// Convert the error into an anyhow error, giving `NeedsAuthorization` failures a message that\n/// tells the user what to do about it (see [`needs_authorization_message`]); every other error\n/// converts as-is.\nfn map_needs_authorization(err: GitError) -> anyhow::Error {\n    let crate::Error::Backend(crate::repository::RepositoryError::NeedsAuthorization(ref prompt)) =\n        err\n    else {\n        return err.into();\n    };","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-git/src/context.rs#L162-L198","documentation":"fetch_with_askpass runs the gix fetch on a dedicated std::thread with its own tokio runtime so askpass prompts are not blocked. If that worker thread panics (instead of returning Err), the join() payload is downcast to a reason and re-raised as this anyhow error wrapped in but_error Context 'git fetch failed unexpectedly' with Code::Unknown. It signals a bug or unrecoverable state inside the fetch stack, not a normal git failure (those come back as Err from the closure).","triggerScenarios":"Any panic inside crate::fetch / gix / tokio runtime code while fetching: index corruption, unsupported pack features hitting unwrap/expect, bugs in a specific gix version, poisoned interior state after the repo was mutated concurrently. The panic message (or 'unknown panic payload') is embedded as {reason}.","commonSituations":"After a gix crate upgrade introduces a panic on certain server responses; corrupted local object database making the pack writer panic; races between fetch and another process writing .git simultaneously; transient panics on malformed server data that disappear on retry.","solutions":["Read the embedded {reason} and the application logs — the panic message names the actual failing code","Retry the fetch once; transient panics on concurrent .git access often clear when the other git process finishes","Run `git fsck` and `git fetch <remote>` with the git CLI in the same worktree to surface repository corruption the panic may hint at","Upgrade GitButler / the gix dependency — panic-on-fetch bugs are usually fixed upstream; if it persists, report an issue with the panic reason and backtrace"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"fn fetch_with_retry(ctx: &Context, remote: &str, askpass: Option<String>) -> Result<()> {\n    match ctx.fetch(remote, askpass.clone()) {\n        Ok(()) => Ok(()),\n        Err(first) if first.to_string().contains(\"fetch worker thread panicked\") => {\n            tracing::warn!(\"fetch panicked, retrying once: {first}\");\n            ctx.fetch(remote, askpass).map_err(|second| first.context(second))\n        }\n        Err(err) => Err(err),\n    }\n}","preventionTips":["Avoid running concurrent git operations that mutate .git while a fetch is in flight; take the app's repository locks","Keep the gitbutler/gix dependency current — fetch panics are usually fixed upstream","Log the panic reason with the fetch arguments so recurring panics can be reported with reproduction data","Run `git fsck` periodically to catch object-database corruption before it panics the fetch writer"],"tags":["git","fetch","panic","threading","gix","gitbutler"],"backgroundTag":"worker-thread-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}