gitbutlerapp/gitbutler · error · anyhow::Error
line {line_number}: invalid squash commit '{commit}': {err}
Error message
line {line_number}: invalid squash commit '{commit}': {err} What it means
A commit inside a `squash <c1> <c2> [...]` line failed the same resolve_commit check against the editable divergence set. The message names the exact offending commit string, so multi-commit squash lines with one bad hash are easy to localize.
Source
Thrown at crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs:85
let commit = arguments
.first()
.copied()
.expect("validated merge arity above");
InteractiveIntegrationStep::Merge {
commit_id: resolve_commit(commit, &allowed_commits).map_err(|err| {
anyhow::anyhow!("line {line_number}: invalid merge commit: {err}")
})?,
}
}
"squash" => {
if arguments.len() < 2 {
bail!("line {line_number}: squash requires at least two commits");
}
let commits = arguments
.iter()
.map(|commit| {
resolve_commit(commit, &allowed_commits).map_err(|err| {
anyhow::anyhow!(
"line {line_number}: invalid squash commit '{commit}': {err}"
)
})
})
.collect::<Result<Vec<_>>>()?;
InteractiveIntegrationStep::Squash {
commits,
message: message_part
.map(parse_message_clause)
.transpose()
.map_err(|err| {
anyhow::anyhow!("line {line_number}: invalid squash message: {err}")
})?,
}
}
other => bail!("line {line_number}: unknown command '{other}'"),
};
steps.push(step);View on GitHub (pinned to caf1f223d3)
Solutions
- Fix the specific commit token named in the error to a hash from the generated script
- Lengthen ambiguous prefixes
- Regenerate the integration script and re-apply the squash edits
Example fix
# before squash 3333333 deadbee 4444444 # deadbee not in divergence # after squash 3333333 4444444
Defensive patterns
Strategy: validation
Validate before calling
// validate every token of each squash line (≥2 tokens, all resolvable)
for line in script.lines().filter(|l| l.trim_start().starts_with("squash")) {
let toks: Vec<_> = line.split_whitespace().collect();
ensure!(toks.len() >= 2, "squash needs ≥2 commits");
for t in &toks[1..] { ensure!(commit_resolves(&candidate_strings, t), "squash token {t} invalid"); }
} Type guard
fn squash_line_valid(line: &str, candidates: &HashSet<String>) -> bool {
let toks: Vec<_> = line.split('|').next().unwrap_or("").split_whitespace().collect();
toks.len() >= 2 && toks[1..].iter().all(|t| commit_resolves(candidates, t))
} Try / catch
if let Err(e) = parse_integration_steps_script(&script, &divergence) {
if let Some(rest) = e.to_string().split("invalid squash commit '").nth(1) { /* rest starts with the offending token — show it */ }
} Prevention
- Squash only commits that appear in the generated script's pick/merge lines
- Use longer prefixes when several candidates share a short prefix
- Re-validate the whole script after each manual edit, not only at submit time
When it happens
Trigger: A squash line listing ≥2 commits where at least one hash is not a not-integrated local commit or upstream-only commit, or one short prefix is ambiguous. Inner error text distinguishes 'not part of the editable divergence' from 'ambiguous'.
Common situations: Hand-editing squash lines and pasting hashes from another branch window; stale scripts reused after upstream force-push changed the candidate set.
Related errors
- line {line_number}: invalid squash message: {err}
- line {line_number}: invalid pick commit: {err}
- line {line_number}: invalid merge commit: {err}
- line {line_number}: squash requires at least two commits
- Squash step must have at least two commits
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/4cd4a5ee0b1eacc3.
Report an issue: GitHub.