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
- Read the per-remote messages to see which remotes failed and why
- Run `git fetch <remote>` manually for the failing remote to reproduce
- Fix credentials (`but config forge auth`) or network/proxy settings
- 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
- Keep remotes and credentials valid; re-auth with `but config forge auth`
- Prune deleted remotes (`git remote prune`, or remove them)
- Check network/proxy before batch fetches
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
- `but merge` does not yet support triangular remotes
- Failed to fetch from RSS feed:
- push failed
- Unknown
- Aborting due to empty branch name
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)