{"record":{"id":"72ce8a35c0c5fa82","repo":"affaan-m/ECC","slug":"git-merge-failed-stderr","errorCode":null,"errorMessage":"git merge failed: {stderr}","messagePattern":"git merge failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ecc2/src/worktree/mod.rs","lineNumber":897,"sourceCode":"    }\n\n    if !git_status_short(&repo_root)?.is_empty() {\n        anyhow::bail!(\n            \"Repository root {} has uncommitted changes; commit or stash them before merging\",\n            repo_root.display()\n        );\n    }\n\n    let output = Command::new(\"git\")\n        .arg(\"-C\")\n        .arg(&repo_root)\n        .args([\"merge\", \"--no-edit\", &worktree.branch])\n        .output()\n        .context(\"Failed to merge worktree branch into base\")?;\n\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\n        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!(","sourceCodeStart":879,"sourceCodeEnd":915,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/worktree/mod.rs#L879-L915","documentation":"Thrown by merge_into_base at ecc2/src/worktree/mod.rs:897 when `git -C <repo_root> merge --no-edit <worktree.branch>` exits non-zero. By this point all pre-flight checks (merge readiness, clean worktree, correct base branch, clean repo root) have passed, so the failure is from git itself — typically a content conflict that the dry-run readiness check did not predict, a ref mismatch, or a hook rejection. The bail forwards git's stderr verbatim.","triggerScenarios":"Conflicts on files modified in both branches since the merge base; pre-merge or prepare-commit-msg hooks that exit non-zero; the worktree branch was deleted or force-pushed between the readiness check and the merge command; large-file issues (LFS smudge failures); author/identity config problems when committer hooks fire.","commonSituations":"Two worktree sessions edited the same files and the second merge conflicts; force-push to the worktree branch changed its tip after merge_readiness ran; a pre-commit hook (lint, format, secret-scan) rejected the merge commit; git LFS not installed or authenticated and a tracked binary file fails to fetch.","solutions":["Inspect the forwarded stderr for the exact conflict path; run `git -C <repo_root> status` to see conflicted files and resolve them, then `git merge --continue` (or `--abort` to roll back).","If a hook rejected the merge, fix the hook's complaint (format the code, remove the secret) or run the merge with `--no-verify` manually if your policy allows it.","Re-run merge_readiness on the worktree right before merge_into_base to catch conflicts that emerged between scheduling and execution.","If the worktree branch was force-pushed, re-sync: rebase the worktree onto the new tip before merging."],"exampleFix":"// before\nlet outcome = merge_into_base(&worktree)?;\n\n// after: surface git's conflict detail and abort cleanly\nmatch merge_into_base(&worktree) {\n    Ok(o) => Ok(o),\n    Err(e) if e.to_string().contains(\"git merge failed\") => {\n        let _ = Command::new(\"git\").arg(\"-C\").arg(&repo_root)\n            .args([\"merge\", \"--abort\"]).status();\n        anyhow::bail!(\"merge of {} aborted; resolve conflicts: {e}\", worktree.branch);\n    }\n    Err(e) => Err(e),\n}","handlingStrategy":"try-catch","validationCode":"// Re-run readiness immediately before merge to catch late conflicts\nlet readiness = merge_readiness(&worktree)?;\nif readiness.status == MergeReadinessStatus::Conflicted {\n    anyhow::bail!(\"pre-merge conflict: {}\", readiness.summary);\n}\nlet repo_root = base_checkout_path(&worktree)?;\nif get_current_branch(&repo_root)? != worktree.base_branch\n    || !git_status_short(&repo_root)?.is_empty() {\n    anyhow::bail!(\"repo root not ready for merge\");\n}","typeGuard":"null","tryCatchPattern":"match merge_into_base(&worktree) {\n    Ok(o) => Ok(o),\n    Err(e) if e.to_string().starts_with(\"git merge failed\") => {\n        let repo_root = base_checkout_path(&worktree)?;\n        let _ = Command::new(\"git\").arg(\"-C\").arg(&repo_root)\n            .args([\"merge\", \"--abort\"]).status();\n        // fall back: rebase the worktree onto base to linearize, then merge\n        let _ = rebase_onto_base(&worktree);\n        Err(e)\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Run merge_readiness within milliseconds of merge_into_base, not minutes.","Disable or relax pre-merge hooks that reject agent-generated commits unless policy mandates them.","Pin the worktree branch tip (record its OID) before merge and verify it is unchanged at merge time.","Keep branches short-lived so the conflict surface stays small."],"tags":["git","merge","conflict","hooks","subprocess-failure"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}