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

  1. Commit or stash the changes: `git add -A && git commit` or `git stash -u`
  2. Discard unneeded changes: `git checkout -- .` / `git clean -fd` after review
  3. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/1d4730d40df83e86. Report an issue: GitHub.