How no-mistakes Handles Merge Conflicts: Detection, Resolution, and Safety

no-mistakes treats merge conflicts as first-class pipeline findings, detecting them during dry rebases or CI checks, then employing an LLM agent to resolve conflict markers with automatic staging and rebasing, protected by force-with-lease safety guards.

The no-mistakes repository provides a pipeline-based automation toolkit for Git workflows that treats merge conflicts as actionable events rather than fatal errors. According to the source code in kunchenguid/no-mistakes, the system implements a multi-layered approach to no-mistakes merge conflicts that spans detection, reporting, automated resolution, and push safety.

Detecting Merge Conflicts During the Rebase Step

The primary detection mechanism occurs in the Rebase step, implemented in internal/pipeline/steps/rebase.go. Before modifying any history, the pipeline performs a dry rebase onto target branches (origin/<branch> and origin/<default>) to test for conflicts.

The tryRebase function attempts the rebase and returns a slice of conflicted files if the operation stops:

// internal/pipeline/steps/rebase.go
conflictFiles, err := tryRebase(ctx, sctx, target)
if len(conflictFiles) > 0 {
    conflictTargets = append(conflictTargets, target)
    for _, file := range conflictFiles {
        conflictFindings = append(conflictFindings,
            Finding{Severity: "warning", File: file,
                    Description: fmt.Sprintf("merge conflict rebasing onto %s", target)})
    }
}

Each conflict is recorded as a Finding with severity warning and a descriptive message indicating the target branch. These findings accumulate across all attempted targets.

Recording Conflicts as Step Outcomes

After attempting all rebase targets, the step constructs a StepOutcome that signals the executor to pause:

  • NeedsApproval: true — indicates human or agent review is required
  • AutoFixable: true — signals that an automated fix may be attempted

This outcome triggers the conflict resolution workflow rather than failing the pipeline.

CI Monitoring for Merge Conflicts

Beyond local rebase detection, the CI step in internal/pipeline/steps/ci.go queries the remote host's mergeable state. When running against GitHub, GitLab, or Bitbucket, the system checks the MergeableState field:

// internal/pipeline/steps/ci.go (excerpt)
if host.Capabilities().MergeableState {
    mergeState, mergeErr := host.GetMergeableState(ctx, pr)
    if mergeErr == nil {
        mergeConflict = mergeState.Conflict()   // true → conflict
        timeoutMergeConflict = mergeConflict
    }
}

If mergeState.Conflict() returns true, the step adds the same "merge conflict" flag to its findings. When the run operates in fixing mode, this detection immediately invokes the agent resolution routine.

Agent-Driven Conflict Resolution

When rebaseWithAgent is invoked (typically in internal/pipeline/steps/rebase.go), the pipeline aborts the current rebase, collects the conflicted file list from rebaseConflictFiles, and constructs a detailed prompt for the LLM agent.

The prompt explicitly instructs the agent to:

  1. Locate and resolve conflict markers (<<<<<<<, =======, >>>>>>>)
  2. Stage resolved files using git add <file>
  3. Complete the rebase with git rebase --continue
// internal/pipeline/steps/rebase.go – the agent prompt
prompt := fmt.Sprintf(`Resolve git rebase conflicts. The rebase of the current branch onto %s has conflicts.

Current conflicted files:
- %s

Instructions:
- Find all conflicting files and resolve the conflict markers (<<<<<<< ======= >>>>>>>).
- After resolving each file, stage it with: git add <file>
- After all conflicts are resolved, run: git rebase --continue
- …`, targetRef, strings.Join(conflictFiles, "\n- "))

The agent must return a JSON response containing a summary field. The rebaseWithAgent function then executes git rebase --continue (or aborts on error) and pushes the resolved history.

Force-Push Safety with Lease Guards

Before pushing resolved conflicts, the pipeline computes a force-push decision via resolveForcePushDecision in internal/pipeline/steps/ci_fix.go. This safety mechanism prevents accidental overwrites of new remote commits:

// internal/pipeline/steps/ci_fix.go
decision, err := resolveForcePushDecision(gitRun, pushURL, ref, newHeadSHA,
                                         sctx.Run.HeadSHA, sctx.Run.BaseSHA)

The decision relies on the last observed remote SHA (sctx.Run.HeadSHA). If the remote has received out-of-band commits since the pipeline began, the push is rejected rather than silently overwriting history. This protects the conflict-resolution workflow from losing work when multiple processes interact with the same branch.

Human-in-the-Loop Fallback

If the agent returns no changes or the conflict persists after resolution attempts, the step returns a finding with ActionAskUser. The terminal UI (axi) displays a parked status, allowing the developer to intervene manually and resolve complex semantic conflicts that require human judgment.

Summary

  • Early Detection: The tryRebase function in internal/pipeline/steps/rebase.go performs dry rebases to detect conflicts before modifying history, recording them as severity warning findings.
  • CI Integration: The CI monitor queries MergeableState via host.GetMergeableState to detect remote conflicts without local rebase operations.
  • Automated Resolution: The rebaseWithAgent function constructs explicit prompts for LLM agents to resolve conflict markers, stage files, and continue rebases.
  • Push Safety: resolveForcePushDecision enforces force-with-lease semantics using Run.HeadSHA to prevent overwriting concurrent remote changes.
  • Graceful Degradation: Unresolvable conflicts trigger ActionAskUser, parking the run for human intervention in the axi interface.

Frequently Asked Questions

How does no-mistakes detect merge conflicts before attempting a destructive rebase?

The pipeline calls tryRebase in internal/pipeline/steps/rebase.go to perform a dry rebase onto target branches. If the rebase stops due to conflicts, the function returns the list of conflicted files, which are recorded as Finding structures with severity warning and descriptions indicating the target branch.

What prevents no-mistakes from overwriting remote commits during conflict resolution?

The resolveForcePushDecision function in internal/pipeline/steps/ci_fix.go compares the new head SHA against sctx.Run.HeadSHA (the last observed remote state). If the remote has diverged, the push is rejected, enforcing force-with-lease semantics that protect against accidental overwrites during automated fixes.

Can no-mistakes resolve merge conflicts without human intervention?

Yes, when running in fixing mode, the system invokes rebaseWithAgent to prompt an LLM agent with the specific conflicted files and resolution instructions. The agent edits the files, stages them with git add, and the pipeline runs git rebase --continue automatically, provided the agent returns a valid JSON summary.

How does the CI step detect merge conflicts independently of the rebase step?

The CI step in internal/pipeline/steps/ci.go leverages host.GetMergeableState to query the Git provider's API (GitHub, GitLab, Bitbucket) for the MergeableState field. When the state equals CONFLICTING, the step flags the conflict and can immediately trigger the agent resolution routine without performing a local rebase.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →