gitbutlerapp/gitbutler · critical
Run the following command in your working directory to fix…
Error message
Run the following command in your working directory to fix this while leaving your worktree unchanged.
Worktree changes need to be re-committed manually for now.
git reset --soft {ws_commit_id} What it means
During conversion of the commit graph to workspace ref info, the library detects an inconsistent workspace state (a managed workspace commit that the graph cannot account for, e.g. the workspace HEAD commit differs from what the managed segments expect). Since it cannot repair this itself, it bails with a user-facing message telling them to run `git reset --soft <ws_commit_id>` to restore the workspace while keeping worktree changes, then re-commit manually.
Solutions
- Follow the error message: run `git reset --soft <ws_commit_id>` in the working directory, then re-commit worktree changes manually
- Avoid raw git history-modifying commands inside GitButler workspaces; use `but` commands instead
- If the state persists, recreate the workspace branch or report the issue with the workspace state
Example fix
// before git rebase main # inside GitButler workspace → breaks managed graph // after but branch rebase # or: git reset --soft <ws_commit_id> and re-commit via but
Defensive patterns
Strategy: try-catch
Try / catch
match result {
Err(e) if e.to_string().contains("git reset --soft") => {
let ws_commit_id = extract_ws_commit(&e.to_string())?;
run_git(&["reset", "--soft", &ws_commit_id])?;
// re-commit worktree changes, then retry
}
other => other?,
} Prevention
- Never run raw git history-rewriting commands (reset, rebase, amend) inside a GitButler workspace
- Use `but` CLI / app commands for all workspace history changes
- Check workspace health after out-of-band git usage before further operations
When it happens
Trigger: The workspace commit was modified out-of-band (git commit --amend/reset/rebase done manually inside the GitButler workspace), leaving graph_to_ref_info with a managed commit whose segment/position no longer matches; corrupted or manually edited GitButler refs.
Common situations: Users running raw git commands (reset, rebase, cherry-pick) inside an active GitButler workspace; interrupted GitButler operations; switching tooling mid-work.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Ad-hoc (single-branch) branch moves are not supported…
- Branch ' ' cannot be created: the target commit ( ) already…
- BranchNotFound
- BUG: failed to apply head ref
- Cannot add the target
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/0a0397018b88f070.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/ref_info.rs:615
.map(|worktree| crate::worktrees::WorktreeInfo::try_from_graph_worktree(worktree, repo))
.collect::<anyhow::Result<_>>()?,
};
if let Some(info) = &info.ancestor_workspace_commit {
// This is the MVP version of what should be guided by the UI - just communicate through
// an error message, which can only be recovered once the command is executed.
let mut msg = format!(
"Found {} commit(s) on top of the workspace commit.\n\n",
info.commits_outside.len()
);
let ws_commit_id =
graph[info.segment_with_managed_commit].commits[info.commit_index_of_managed_commit].id;
msg.push_str(
"Run the following command in your working directory to fix this while leaving your worktree unchanged.\n",
);
msg.push_str("Worktree changes need to be re-committed manually for now.\n\n");
msg.push_str(&format!(" git reset --soft {ws_commit_id}"));
bail!("{msg}");
}
info.compute_similarity(graph, repo, opts.expensive_commit_info)?;
if let GerritMode::Enabled(metadata) = opts.gerrit_mode {
info.apply_gerrit_metadata(metadata)?;
}
Ok(info)
}
/// Set (or clear) the forge review association exposed on a segment's metadata.
///
/// `cached` is the review the forge cache resolves for the segment, as
/// `(number, is open, merged head)`, or `None` when nothing matched. The whole
/// association rule lives here: an integrated branch keeps its stored review
/// number — the durable identity of the review that landed it — with any
/// cached match as a fallback for numbers persisted before this existed.
/// Every other branch associates with an OPEN cached review, or one merged
/// exactly at `tip` — a merge whose integration the graph has not yet seen —
/// so continuing work after a merge does not revive the old review as anView on GitHub (pinned to 58e5313667)