xai-org/grok-build · error · anyhow::Error
working tree has uncommitted changes; commit or stash before
Error message
working tree has uncommitted changes; commit or stash before switching branches
What it means
checkout_branch() runs `git status --porcelain` (with submodules excluded) via libgit2 and refuses to switch branches when the working tree is dirty. This prevents losing or entangling uncommitted changes during ensure_binding/merge_to_main flows.
Source
Thrown at crates/codegen/xai-grok-workspace/src/session/git.rs:602
}
/// Switch the working tree to a different branch, optionally creating it.
///
/// Refuses to switch if the working tree is dirty (staged or unstaged changes)
/// to avoid losing work. The dirty check uses `git2` (no subprocess).
pub async fn checkout_branch(git_root: &Path, branch: &str, create: bool) -> Result<()> {
let root = git_root.to_path_buf();
let has_changes = tokio::task::spawn_blocking(move || -> Result<bool> {
let repo = Repository::discover(&root)?;
let mut opts = StatusOptions::new();
opts.include_untracked(false)
.include_ignored(false)
.exclude_submodules(true);
let statuses = repo.statuses(Some(&mut opts))?;
Ok(!statuses.is_empty())
})
.await??;
if has_changes {
anyhow::bail!(
"working tree has uncommitted changes; commit or stash before switching branches"
);
}
if create {
git_cli_mut(git_root, &["checkout", "-b", branch]).await?;
} else {
git_cli_mut(git_root, &["checkout", branch]).await?;
}
Ok(())
}
async fn get_upstream(cwd: &Path) -> Option<String> {
git_cli(cwd, &["rev-parse", "--abbrev-ref", "@{upstream}"])
.await
.ok()
}
async fn get_remote_url(cwd: &Path) -> Option<String> {
git_cli(cwd, &["remote", "get-url", "origin"]).await.ok()
}View on GitHub (pinned to bc7f02eddd)
Solutions
- Commit or stash the changes: `git add -A && git commit` or `git stash -u`
- Discard unneeded changes: `git checkout -- .` / `git clean -fd` after review
- Commit programmatically in the session flow before calling checkout_branch
Example fix
// before
ensure_binding(git_root, branch).await?; // fails if dirty
// after
if working_tree_dirty(git_root) { git_cli(git_root, &["stash", "push", "-u"]).await?; }
ensure_binding(git_root, branch).await?; Defensive patterns
Strategy: validation
Validate before calling
let dirty = std::process::Command::new("git").arg("-C").arg(git_root).args(["status","--porcelain"]).output()?;
if !dirty.stdout.is_empty() { anyhow::bail!("commit or stash before switching branches"); } Type guard
fn working_tree_clean(git_root: &Path) -> bool {
std::process::Command::new("git").arg("-C").arg(git_root).args(["status","--porcelain"])
.output().map(|o| o.status.success() && o.stdout.is_empty()).unwrap_or(false)
} Try / catch
match checkout_branch(git_root, branch, false).await {
Err(e) if e.to_string().contains("uncommitted changes") => {
git_cli(git_root, &["stash", "push", "-u"]).await?;
checkout_branch(git_root, branch, false).await?;
}
other => other?,
} Prevention
- Run git status --porcelain before any branch switch
- Auto-stash (with -u for untracked) in automation before checkout
- Commit generated artifacts promptly instead of leaving them dirty
- Exclude untracked build outputs via .gitignore so they don't block flows
When it happens
Trigger: Calling checkout_branch (directly or through ensure_binding / merge_to_main) while modified, staged, or untracked-tracked files exist in the worktree.
Common situations: Developer left edits or generated files in the worktree before publishing/merging; interrupted previous session left partial changes; build artifacts tracked by git.
Related errors
- git checkout {} failed: {}
- Working tree is dirty. Commit changes first, or use --force.
- merge --abort failed: {out}
- git reset --hard {} failed: {}
- git clean {} failed: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/1d4730d40df83e86.
Report an issue: GitHub.