gitbutlerapp/gitbutler · error

{remote}: {err}

Error message

{remote}: {err}

What it means

`workspace_fetch_from_remotes` aggregates failures from fetching multiple remotes. When any remote fetch fails, it joins all per-remote errors as '{remote}: {err}' lines into a single anyhow error, optionally attaching the first available error code as context.

Solutions

  1. Read the per-remote messages to see which remotes failed and why
  2. Run `git fetch <remote>` manually for the failing remote to reproduce
  3. Fix credentials (`but config forge auth`) or network/proxy settings
  4. Remove or fix stale remotes via `git remote` if a remote no longer exists
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check remotes are reachable/configured
for remote in repo.remotes()? {
    let url = repo.find_remote(&remote)?.url().map(String::from);
    if url.is_none() { eprintln!("remote {remote} has no URL"); }
}

Try / catch

match workspace_fetch(ctx).await {
    Err(e) => {
        // message contains per-remote lines: "remote: err"
        for line in format!("{e:#}").lines() {
            eprintln!("fetch failure: {line}");
        }
        Err(e)
    }
    ok => ok,
}

Prevention

When it happens

Trigger: Calling `workspace_fetch` when one or more configured remotes fail to fetch: network unreachable, bad credentials, missing remote, rejected refs, or remote-side errors.

Common situations: Offline work; expired forge credentials; a remote was deleted server-side; DNS/proxy issues; a colleague force-pushed causing fetch rejections.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/9d724363acecaf4c. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-api/src/workspace.rs:323

            return Ok(Some(ignored));
        }

        // Keep the failure's own error context (e.g. the `ProjectGitAuth` code and its
        // user-facing message) intact whenever possible: return a single failure as-is, and
        // reapply the first failure's code when several failures collapse into one message.
        Err(if failures.len() == 1 {
            let (remote, err) = failures.into_iter().next().expect("length checked above");
            err.context(format!("fetching remote `{remote}` failed"))
        } else {
            let code = failures
                .iter()
                .find_map(|(_, err)| err.custom_context().map(|ctx| ctx.code));
            let joined = failures
                .iter()
                .map(|(remote, err)| format!("{remote}: {err}"))
                .collect::<Vec<_>>()
                .join("\n");
            let err = anyhow::anyhow!(joined);
            match code {
                Some(code) => err.context(code),
                None => err,
            }
        })
    })();

    let attempted_ms = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|err| anyhow::anyhow!("system clock is before the Unix epoch: {err}"))?
        .as_millis()
        .try_into()
        .map_err(|err| anyhow::anyhow!("fetch timestamp does not fit in the database: {err}"))?;
    let _guard = ctx.exclusive_worktree_access();
    match &fetch_result {
        Ok(ignored_errors) => ctx
            .db
            .get_cache_mut()?

View on GitHub (pinned to 58e5313667)