gitbutlerapp/gitbutler · error · anyhow::Error

line {line_number}: invalid squash message: {err}

Error message

line {line_number}: invalid squash message: {err}

What it means

Only squash accepts an optional message clause, written after a '|' separator as `| message="..."` with a double-quoted string and only \n \r \t \" \\ escapes. parse_message_clause (or its quoted-string parser) rejected the clause: missing 'message=' prefix, missing/incorrect opening quote, unsupported escape, trailing characters after the closing quote, or an unterminated quote/escape.

Source

Thrown at crates/but-workspace/src/branch/integrate_branch_upstream/parsing.rs:97

                    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);
    }

    Ok(steps)
}

fn render_step(step: &InteractiveIntegrationStep) -> String {
    match step {
        InteractiveIntegrationStep::Pick { commit_id } => format!("pick {}", short_id(*commit_id)),
        InteractiveIntegrationStep::Merge { commit_id } => {
            format!("merge {}", short_id(*commit_id))
        }
        InteractiveIntegrationStep::Squash { commits, message } => {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Use the exact form: | message="your text" immediately after the commit list
  2. Escape only \n, \r, \t, \", \\ inside the quotes; put nothing after the closing quote
  3. Omit the clause entirely to keep the default squash message

Example fix

# before
squash 3333333 4444444 | msg='combined fix'   # wrong key and quotes
# after
squash 3333333 4444444 | message="combined fix"
Defensive patterns

Strategy: validation

Validate before calling

// validate the message clause shape before parsing the script
fn message_clause_ok(clause: &str) -> bool {
    let Some(v) = clause.strip_prefix("message=") else { return false };
    v.starts_with('"') && v.ends_with('"') && v.len() >= 2
        && !v[1..v.len()-1].matches('\\').any(|_| { /* escapes limited to n r t \ " — full check in parse_quoted_string */ false })
}

Type guard

fn is_valid_message_clause(clause: &str) -> bool {
    match clause.strip_prefix("message=") {
        Some(v) => v.len() >= 2 && v.starts_with('"') && v.ends_with('"'),
        None => false,
    }
}

Try / catch

if let Err(e) = parse_integration_steps_script(&script, &divergence) {
    if e.to_string().contains("invalid squash message") { /* show expected format: | message="..." */ }
}

Prevention

When it happens

Trigger: Script lines like `squash 3333333 4444444 | msg="x"` (wrong key), `... | message='x'` (single quotes), `... | message="a\db"` (bad escape), or `... | message="a" trailing` (trailing text).

Common situations: Users writing rebase-style messages ('pick ... # message') instead of the pipe format; escaping quotes incorrectly when hand-editing.

Related errors


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