gitbutlerapp/gitbutler · error · anyhow::Error

The model returned no reasoning for "{}"

Error message

The model returned no reasoning for "{}"

What it means

Every per-file resolution from the model must include non-empty `reasoning` (trimmed). GitButler requires the model to justify each resolution so it can be surfaced to the user; an empty or whitespace-only reasoning string fails the whole response. This is a contract of ResolutionResponse, not a nicety.

Source

Thrown at crates/but-api/src/resolve/mod.rs:640

            );
        };
        if resolved_files[index].is_some() {
            bail!(
                "The model returned more than one resolution for \"{}\"",
                resolution.path
            );
        }
        let file = &request.files[index];
        if resolution.hunks.len() != file.hunks.len() {
            bail!(
                "The model returned {} resolved hunks for \"{}\" but the file has {} conflicts",
                resolution.hunks.len(),
                file.path,
                file.hunks.len()
            );
        }
        if resolution.reasoning.trim().is_empty() {
            bail!("The model returned no reasoning for \"{}\"", file.path);
        }
        for (hunk_index, hunk) in resolution.hunks.iter().enumerate() {
            apply::ensure_no_markers(&hunk.resolved_content, &file.path)?;
            picks[index].insert(
                hunk_index,
                apply::HunkPick::Content(hunk.resolved_content.clone()),
            );
        }
        resolved_files[index] = Some(ResolvedFile {
            path: file.path.clone(),
            hunks: resolution
                .hunks
                .iter()
                .map(|hunk| hunk.resolved_content.clone())
                .collect(),
            reasoning: resolution.reasoning.clone(),
        });
    }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Let the single automatic retry run - second samples usually include reasoning
  2. Use a model/provider combination that reliably fills structured text fields
  3. If you inject resolutions via resolve_commit_conflicts_with (tests, custom frontends), always populate a non-empty reasoning per file

Example fix

// before (custom resolver injected into resolve_commit_conflicts_with)
return { resolutions: files.map(f => ({ path: f.path, hunks: [...], reasoning: ' ' })), summary: '' };

// after
return { resolutions: files.map(f => ({ path: f.path, hunks: [...], reasoning: 'Took theirs: our side only deleted a comment.' })), summary: 'Resolved all files.' };
Defensive patterns

Strategy: retry

Try / catch

try {
  await api.resolveCommitConflictsAi(commitId);
} catch (err) {
  if (String(err).includes('no reasoning')) {
    await api.resolveCommitConflictsAi(commitId); // second sample usually fills reasoning
  } else throw err;
}

Prevention

When it happens

Trigger: The model omits the reasoning field, outputs an empty string, or fills it with whitespace/newlines only. Providers whose structured-output enforcement is lax are the usual source; small local models often skip optional-looking text fields.

Common situations: Local/small models via custom providers; schema-optional JSON modes; reasoning hidden behind a provider flag and serialized as an empty string.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/95f763c26afa8199. Report an issue: GitHub.