gitbutlerapp/gitbutler · error · anyhow::Error

line {line_number}: unknown command '{other}'

Error message

line {line_number}: unknown command '{other}'

What it means

Raised by `parse_integration_steps_script` when the first token of a non-empty, non-comment line is not `pick`, `merge`, or `squash`. The integration grammar is deliberately much smaller than git rebase's todo grammar; there is no drop/reword/edit/fixup/exec.

Source

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

                    .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 } => {
            let mut rendered = format!(
                "squash {}",
                commits
                    .iter()

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Map your intent to the supported verbs: omit lines you don't want (there is no drop — deleting the line drops the commit), use pick/merge/squash only.
  2. Put explanatory text behind a leading `#` so it is treated as a comment.
  3. Re-generate the baseline with `render_integration_steps_script(&initial_steps)` and edit from that.

Example fix

# before
reword 4f2a9c1

# after
# (delete the line to skip the commit, or keep it as:)
pick 4f2a9c1
Defensive patterns

Strategy: validation

Validate before calling

fn is_known_command(cmd: &str) -> bool { matches!(cmd, "pick" | "merge" | "squash") }
// lint pass before parse:
for (i, line) in script.lines().enumerate() {
    let t = line.trim();
    if t.is_empty() || t.starts_with('#') { continue; }
    if let Some(cmd) = t.split_whitespace().next() {
        if !is_known_command(cmd) { warn_user_line(i + 1, cmd); }
    }
}

Try / catch

if let Err(err) = parse_integration_steps_script(&script, &divergence) {
    // 'line {n}: unknown command ...' — offer mapping hints (drop -> delete line, reword -> squash message)
}

Prevention

When it happens

Trigger: Lines like `drop 4f2a9c1`, `reword 4f2a9c1`, `edit 4f2a9c1`, `fixup 4f2a9c1`, `exec make`, or a stray non-comment sentence. Empty lines and lines starting with `#` are skipped, so comments are the only annotation allowed.

Common situations: Pasting a `git rebase -i` todo into the integration editor; muscle-memory rebase commands; tools that emit rebase todos reused for this script.

Related errors


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