gitbutlerapp/gitbutler · error · ExplainedRejection

Cannot {verb}: {rejected}

Error message

Cannot {verb}: {rejected}

What it means

Fallback message from the rejection explainer in but's utils: an operation failed because of RejectedChanges (edits that depend on workspace commits or otherwise cannot be applied), and additionally the workspace projection needed to produce a detailed report could not be loaded. You get the terse 'Cannot <verb>: N rejected changes' form instead of the enriched report, with the load failure only logged as a warning.

Source

Thrown at crates/but/src/utils/rejection.rs:100

pub fn explain_after_rollback(
    ctx: &Context,
    perm: &mut RepoExclusive,
    verb: &str,
    target: Target,
    err: anyhow::Error,
) -> anyhow::Error {
    let rejected = match err.downcast::<RejectedChanges>() {
        Ok(rejected) => rejected,
        Err(other) => return other,
    };

    // Explaining a failure must never mask it: without a workspace projection,
    // fall back to the plain rejection count.
    let (repo, ws, _db) = match ctx.workspace_and_db_with_perm(perm.read_permission()) {
        Ok(loaded) => loaded,
        Err(load_err) => {
            tracing::warn!(?load_err, "Failed to load workspace to explain rejections");
            return anyhow::Error::new(ExplainedRejection(format!("Cannot {verb}: {rejected}")));
        }
    };
    let (repo, ws) = (&repo, &ws);

    let target_branch = match &target {
        Target::Commit(commit) => branch_of_commit(ws, commit.commit_id, None),
        Target::Branch(name) => Some(name.clone()),
        Target::NewBranch(_) => None,
    };

    let changes = match explain_rejections(repo, ws, &rejected.0, target_branch.as_deref()) {
        Ok(changes) => changes,
        Err(other) => return other,
    };

    let mut message = format!("Cannot {verb}: {rejected}:\n");
    // Writing to a String cannot fail.
    let _ = write_report(&mut message, &changes, &target, target_branch.as_deref());

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Retry the operation once the concurrent process/lock is gone; with the workspace loadable you will get the full report variant of this error instead.
  2. Check the logs for the companion 'Failed to load workspace to explain rejections' warning — that load error is the real second failure.
  3. Address the underlying rejection as with the detailed variant: the changed paths depend on commits not selected by the target (include those commits or move them first).
  4. If this reproduces deterministically with no concurrency, report it — the explanation path should not silently lose the report.
Defensive patterns

Strategy: retry

Try / catch

try {
  await api.applyChanges(...);
} catch (e) {
  const m = String(e?.message ?? e);
  if (/Cannot .*rejected changes/.test(m) && !m.includes(':\n')) {
    // terse fallback form: workspace projection failed too — retry once without concurrency
    return retryOnceAfterIdle(() => api.applyChanges(...));
  }
  throw e;
}

Prevention

When it happens

Trigger: Applying/moving/merging changes hits rejected hunks AND ctx.workspace_and_db_with_perm() fails at explanation time (repo lock held elsewhere, workspace ref unreadable mid-operation, concurrent modification).

Common situations: Two operations racing on the same project (two app windows, CLI plus app); the workspace ref changed between the failed operation and the explanation pass; degraded repository state where loading the projection legitimately fails.

Related errors


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