{"record":{"id":"1d4730d40df83e86","repo":"xai-org/grok-build","slug":"working-tree-has-uncommitted-changes-commit-or-st","errorCode":null,"errorMessage":"working tree has uncommitted changes; commit or stash before switching branches","messagePattern":"working tree has uncommitted changes; commit or stash before switching branches","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-workspace/src/session/git.rs","lineNumber":602,"sourceCode":"}\n/// Switch the working tree to a different branch, optionally creating it.\n///\n/// Refuses to switch if the working tree is dirty (staged or unstaged changes)\n/// to avoid losing work. The dirty check uses `git2` (no subprocess).\npub async fn checkout_branch(git_root: &Path, branch: &str, create: bool) -> Result<()> {\n    let root = git_root.to_path_buf();\n    let has_changes = tokio::task::spawn_blocking(move || -> Result<bool> {\n        let repo = Repository::discover(&root)?;\n        let mut opts = StatusOptions::new();\n        opts.include_untracked(false)\n            .include_ignored(false)\n            .exclude_submodules(true);\n        let statuses = repo.statuses(Some(&mut opts))?;\n        Ok(!statuses.is_empty())\n    })\n    .await??;\n    if has_changes {\n        anyhow::bail!(\n            \"working tree has uncommitted changes; commit or stash before switching branches\"\n        );\n    }\n    if create {\n        git_cli_mut(git_root, &[\"checkout\", \"-b\", branch]).await?;\n    } else {\n        git_cli_mut(git_root, &[\"checkout\", branch]).await?;\n    }\n    Ok(())\n}\nasync fn get_upstream(cwd: &Path) -> Option<String> {\n    git_cli(cwd, &[\"rev-parse\", \"--abbrev-ref\", \"@{upstream}\"])\n        .await\n        .ok()\n}\nasync fn get_remote_url(cwd: &Path) -> Option<String> {\n    git_cli(cwd, &[\"remote\", \"get-url\", \"origin\"]).await.ok()\n}","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-workspace/src/session/git.rs#L584-L620","documentation":"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.","triggerScenarios":"Calling checkout_branch (directly or through ensure_binding / merge_to_main) while modified, staged, or untracked-tracked files exist in the worktree.","commonSituations":"Developer left edits or generated files in the worktree before publishing/merging; interrupted previous session left partial changes; build artifacts tracked by git.","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"],"exampleFix":"// before\nensure_binding(git_root, branch).await?; // fails if dirty\n// after\nif working_tree_dirty(git_root) { git_cli(git_root, &[\"stash\", \"push\", \"-u\"]).await?; }\nensure_binding(git_root, branch).await?;","handlingStrategy":"validation","validationCode":"let dirty = std::process::Command::new(\"git\").arg(\"-C\").arg(git_root).args([\"status\",\"--porcelain\"]).output()?;\nif !dirty.stdout.is_empty() { anyhow::bail!(\"commit or stash before switching branches\"); }","typeGuard":"fn working_tree_clean(git_root: &Path) -> bool {\n    std::process::Command::new(\"git\").arg(\"-C\").arg(git_root).args([\"status\",\"--porcelain\"])\n        .output().map(|o| o.status.success() && o.stdout.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match checkout_branch(git_root, branch, false).await {\n    Err(e) if e.to_string().contains(\"uncommitted changes\") => {\n        git_cli(git_root, &[\"stash\", \"push\", \"-u\"]).await?;\n        checkout_branch(git_root, branch, false).await?;\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["git","checkout","dirty-worktree","state"],"backgroundTag":"uncommitted-changes","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}