{"record":{"id":"571c5354d125490c","repo":"gitbutlerapp/gitbutler","slug":"the-conflict-in-cannot-be-resolved-automatica","errorCode":null,"errorMessage":"The conflict in \"{}\" cannot be resolved automatically: {} Resolve this commit in edit mode instead.","messagePattern":"The conflict in \"(.+?)\" cannot be resolved automatically: (.+?) Resolve this commit in edit mode instead\\.","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-api/src/resolve/mod.rs","lineNumber":555,"sourceCode":"/// malformed model output) or if its response fails validation against the\n/// request.\npub fn resolve_commit_conflicts_with(\n    ctx: &mut but_ctx::Context,\n    commit_id: gix::ObjectId,\n    dry_run: DryRun,\n    resolve: impl Fn(&ResolutionRequest) -> anyhow::Result<ResolutionResponse>,\n) -> anyhow::Result<AiResolutionResult> {\n    let request = {\n        let _guard = ctx.shared_worktree_access();\n        let repo = ctx.repo.get()?;\n        context::build_request(&repo, commit_id)?\n    };\n    // This path promises a fully resolved commit, so a file the model cannot be\n    // shown is a hard stop rather than something to leave behind — unlike\n    // `resolve_commit_conflict_hunks()`, which resolves what it is asked to and\n    // leaves the rest conflicted by design.\n    if let Some(file) = request.manual.first() {\n        bail!(\n            \"The conflict in \\\"{}\\\" cannot be resolved automatically: {} Resolve this commit in edit mode instead.\",\n            file.path,\n            file.reason\n        );\n    }\n\n    // The model call happens without any worktree lock; the request is plain\n    // data and the apply step below re-reads the workspace under the exclusive\n    // lock, so it fails closed if the commit changed in the meantime.\n    let ((picks, files), summary) = retry_once(|| {\n        let response = resolve(&request)?;\n        let validated = validate_ai_response(&request, &response)?;\n        Ok((validated, response.summary))\n    })?;\n\n    let mut guard = ctx.exclusive_worktree_access();\n    // The workspace graph may have been cached before or during the model\n    // call; the commit-presence check in apply must run against the state","sourceCodeStart":537,"sourceCodeEnd":573,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-api/src/resolve/mod.rs#L537-L573","documentation":"resolve_commit_conflicts_ai() (via resolve_commit_conflicts_with) promises a FULLY resolved commit in one shot, so any file in the request's `manual` list - conflicts with no marker block to splice a resolution into: side deletions, non-blob entries, binary or oversized files - is a hard stop before the model is even called. Only the first manual file is reported; the message points to edit mode. This differs from resolve_commit_conflict_hunks(), which resolves what it is asked to and deliberately leaves the rest conflicted.","triggerScenarios":"Calling resolve_commit_conflicts_ai on a commit whose conflict set includes a file deleted on one side, a binary (image, font, some lockfiles), a submodule, or an oversized file. The request builder sorts those into `manual`, and the first one aborts the AI path.","commonSituations":"Merge conflicts involving assets/binaries; delete-vs-edit conflicts; submodules; very large generated files; users clicking 'Resolve with AI' expecting it to also handle structural conflicts.","solutions":["Pre-check with commit_conflicts(commit_id): a non-empty `manual` list means AI full-resolve will refuse","Resolve the manual file(s) yourself in edit mode, then optionally AI-resolve the remainder","Or use resolve_commit_conflict_hunks for the hunk-addressable conflicts - it leaves manual conflicts behind by design and reports them in its result"],"exampleFix":"// before\nawait api.resolveCommitConflictsAi(commitId); // throws on the first binary/deletion conflict\n\n// after\nconst c = await api.commitConflicts(commitId);\nif (c.manual.length > 0) {\n  showManualResolution(c.manual); // edit mode for binaries/deletions\n} else {\n  await api.resolveCommitConflictsAi(commitId);\n}","handlingStrategy":"validation","validationCode":"// Gate the AI full-resolve on manual being empty\nconst c = await api.commitConflicts(commitId);\nif (c.manual.length > 0) {\n  routeManualFilesToEditMode(c.manual);\n} else {\n  await api.resolveCommitConflictsAi(commitId);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.resolveCommitConflictsAi(commitId);\n} catch (err) {\n  if (String(err).includes('cannot be resolved automatically')) {\n    openEditModeFor(commitId); // manual files present - AI path will never succeed\n  } else throw err;\n}","preventionTips":["Surface the manual list from commit_conflicts (or HunkResolutionResult.manual) in the UI so users see binaries/deletions before clicking AI resolve","Remember only the FIRST manual file is reported - fix or pre-check all of them","For commits with manual conflicts, use per-hunk resolve for the rest instead of the all-or-nothing AI call"],"tags":["conflict-resolution","ai-resolution","manual-resolution","binary-files","resolve-api"],"backgroundTag":"manual-resolution-required","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}