{"record":{"id":"298e332744408667","repo":"gitbutlerapp/gitbutler","slug":"cannot-verb-rejected","errorCode":null,"errorMessage":"Cannot {verb}: {rejected}","messagePattern":"Cannot (.+?): (.+?)","errorType":"exception","errorClass":"ExplainedRejection","httpStatus":null,"severity":"error","filePath":"crates/but/src/utils/rejection.rs","lineNumber":100,"sourceCode":"pub fn explain_after_rollback(\n    ctx: &Context,\n    perm: &mut RepoExclusive,\n    verb: &str,\n    target: Target,\n    err: anyhow::Error,\n) -> anyhow::Error {\n    let rejected = match err.downcast::<RejectedChanges>() {\n        Ok(rejected) => rejected,\n        Err(other) => return other,\n    };\n\n    // Explaining a failure must never mask it: without a workspace projection,\n    // fall back to the plain rejection count.\n    let (repo, ws, _db) = match ctx.workspace_and_db_with_perm(perm.read_permission()) {\n        Ok(loaded) => loaded,\n        Err(load_err) => {\n            tracing::warn!(?load_err, \"Failed to load workspace to explain rejections\");\n            return anyhow::Error::new(ExplainedRejection(format!(\"Cannot {verb}: {rejected}\")));\n        }\n    };\n    let (repo, ws) = (&repo, &ws);\n\n    let target_branch = match &target {\n        Target::Commit(commit) => branch_of_commit(ws, commit.commit_id, None),\n        Target::Branch(name) => Some(name.clone()),\n        Target::NewBranch(_) => None,\n    };\n\n    let changes = match explain_rejections(repo, ws, &rejected.0, target_branch.as_deref()) {\n        Ok(changes) => changes,\n        Err(other) => return other,\n    };\n\n    let mut message = format!(\"Cannot {verb}: {rejected}:\\n\");\n    // Writing to a String cannot fail.\n    let _ = write_report(&mut message, &changes, &target, target_branch.as_deref());","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/utils/rejection.rs#L82-L118","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await api.applyChanges(...);\n} catch (e) {\n  const m = String(e?.message ?? e);\n  if (/Cannot .*rejected changes/.test(m) && !m.includes(':\\n')) {\n    // terse fallback form: workspace projection failed too — retry once without concurrency\n    return retryOnceAfterIdle(() => api.applyChanges(...));\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["rust","gitbutler","workspace","rejected-changes","fallback"],"backgroundTag":"operation-rejected-by-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}