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
- 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.
- Check the logs for the companion 'Failed to load workspace to explain rejections' warning — that load error is the real second failure.
- 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).
- 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
- Avoid running two mutating operations on the same project concurrently.
- When you see the terse form, check logs for 'Failed to load workspace to explain rejections' — two failures are stacked.
- Retry once serially; the explained variant gives actionable output if it recurs.
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
- Cannot {verb}: {rejected}:
- When using OpenRouter, you must provide a valid API key
- HTTP Error ${response.statusText}: ${text}
- Tearing off a branch requires a workspace common base
- Refusing to delete last named segment '{}' as it would leave
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/298e332744408667.
Report an issue: GitHub.