gitbutlerapp/gitbutler · error

Unknown

Unknown

Error message

git fetch worker thread panicked: {reason}

What it means

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).

Source

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

        )?;
        Ok(runtime.block_on(crate::fetch(
            repo_path,
            crate::tokio::TokioExecutor,
            &remote,
            on_prompt,
        )))
    })
    .join()
    .map_err(|panic| {
        let reason = if let Some(message) = panic.downcast_ref::<String>() {
            message.clone()
        } else if let Some(message) = panic.downcast_ref::<&'static str>() {
            (*message).to_owned()
        } else {
            "unknown panic payload".to_owned()
        };

        anyhow!("git fetch worker thread panicked: {reason}").context(
            but_error::Context::new("git fetch failed unexpectedly").with_code(Code::Unknown),
        )
    })??;
    result.map_err(map_needs_authorization)
}

/// The concrete error type produced by fetch/push through the tokio executor.
type GitError = crate::Error<crate::repository::Error<crate::tokio::TokioExecutor>>;

/// Convert the error into an anyhow error, giving `NeedsAuthorization` failures a message that
/// tells the user what to do about it (see [`needs_authorization_message`]); every other error
/// converts as-is.
fn map_needs_authorization(err: GitError) -> anyhow::Error {
    let crate::Error::Backend(crate::repository::RepositoryError::NeedsAuthorization(ref prompt)) =
        err
    else {
        return err.into();
    };

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Read the embedded {reason} and the application logs — the panic message names the actual failing code
  2. Retry the fetch once; transient panics on concurrent .git access often clear when the other git process finishes
  3. Run `git fsck` and `git fetch <remote>` with the git CLI in the same worktree to surface repository corruption the panic may hint at
  4. 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
Defensive patterns

Strategy: retry

Try / catch

fn fetch_with_retry(ctx: &Context, remote: &str, askpass: Option<String>) -> Result<()> {
    match ctx.fetch(remote, askpass.clone()) {
        Ok(()) => Ok(()),
        Err(first) if first.to_string().contains("fetch worker thread panicked") => {
            tracing::warn!("fetch panicked, retrying once: {first}");
            ctx.fetch(remote, askpass).map_err(|second| first.context(second))
        }
        Err(err) => Err(err),
    }
}

Prevention

When it happens

Trigger: 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}.

Common situations: 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.

Related errors


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