{"record":{"id":"c90e32264fc0d419","repo":"affaan-m/ECC","slug":"worktree-has-uncommitted-changes-commit-or-dis-c90e32","errorCode":null,"errorMessage":"Worktree {} has uncommitted changes; commit or discard them before rebasing","messagePattern":"Worktree (.+?) has uncommitted changes; commit or discard them before rebasing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ecc2/src/worktree/mod.rs","lineNumber":915,"sourceCode":"        anyhow::bail!(\"git merge failed: {stderr}\");\n    }\n\n    let merged_output = format!(\n        \"{}\\n{}\",\n        String::from_utf8_lossy(&output.stdout),\n        String::from_utf8_lossy(&output.stderr)\n    );\n\n    Ok(MergeOutcome {\n        branch: worktree.branch.clone(),\n        base_branch: worktree.base_branch.clone(),\n        already_up_to_date: merged_output.contains(\"Already up to date.\"),\n    })\n}\n\npub fn rebase_onto_base(worktree: &WorktreeInfo) -> Result<RebaseOutcome> {\n    if has_uncommitted_changes(worktree)? {\n        anyhow::bail!(\n            \"Worktree {} has uncommitted changes; commit or discard them before rebasing\",\n            worktree.branch\n        );\n    }\n\n    let repo_root = base_checkout_path(worktree)?;\n    let before_head = branch_head_oid_in_repo(&repo_root, &worktree.branch)?;\n    let output = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&worktree.path)\n        .args([\"rebase\", &worktree.base_branch])\n        .output()\n        .context(\"Failed to rebase worktree branch onto base\")?;\n\n    if !output.status.success() {\n        let abort_output = Command::new(\"git\")\n            .arg(\"-C\")\n            .arg(&worktree.path)","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L897-L933","documentation":"Thrown by rebase_onto_base at ecc2/src/worktree/mod.rs:915 as the very first guard. It calls has_uncommitted_changes(worktree) (which runs git status --porcelain in the worktree path) and bails if anything is dirty, because `git rebase <base_branch>` requires a clean working tree — git itself would refuse or, worse, stash-and-partially-replay.","triggerScenarios":"Calling rebase_onto_base on a worktree where the agent left uncommitted edits; an editor or build tool modified tracked files inside the worktree path; staged changes exist (git status -s returns non-empty); untracked files are present (status --porcelain lists them with ??).","commonSituations":"Agent session wrote code to the worktree but did not commit before the user invoked rebase; formatter reformatted files on save after the last commit; leftover conflict markers from a previous rebase attempt; build artifacts (dist/, target/) are tracked or appear as untracked-but-relevant.","solutions":["Commit the worktree's changes first: `git -C <worktree_path> add -A && git -C <worktree_path> commit -m 'wip'`, then retry rebase_onto_base.","If the changes are unwanted, discard them: `git -C <worktree_path> checkout -- .` (and clean untracked files if appropriate).","If you want to keep edits but not commit, git stash inside the worktree before rebasing and pop after: `git -C <worktree_path> stash`.","Ensure ECC's session lifecycle commits agent edits before exposing a rebase entry point."],"exampleFix":"// before\nrebase_onto_base(&worktree)?;\n\n// after: auto-commit dirty worktree before rebasing\nif has_uncommitted_changes(&worktree)? {\n    Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n        .args([\"add\", \"-A\"]).status()?;\n    Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n        .args([\"commit\", \"-m\", \"wip: pre-rebase snapshot\"]).status()?;\n}\nrebase_onto_base(&worktree)?;","handlingStrategy":"validation","validationCode":"if has_uncommitted_changes(&worktree)? {\n    Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n        .args([\"add\", \"-A\"]).status()?;\n    Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n        .args([\"commit\", \"-m\", \"chore: snapshot before rebase\"]).status()?;\n}\n// now safe to call rebase_onto_base","typeGuard":"null","tryCatchPattern":"match rebase_onto_base(&worktree) {\n    Ok(o) => o,\n    Err(e) if e.to_string().contains(\"uncommitted changes; commit or discard them before rebasing\") => {\n        Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n            .args([\"add\", \"-A\"]).status()?;\n        Command::new(\"git\").arg(\"-C\").arg(&worktree.path)\n            .args([\"commit\", \"-m\", \"wip\"]).status()?;\n        rebase_onto_base(&worktree)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Make ECC sessions auto-commit before exposing rebase/merge entry points.","Block external editors from writing to worktree paths during rebase windows.","Add a UI indicator showing dirty/clean state on the worktree before the rebase button is enabled.","Run rebase inside a tmux/lock guard so concurrent invocations serialize."],"tags":["git","worktree","rebase","dirty-tree","precondition"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}