gitbutlerapp/gitbutler · warning · ExplainedRejection

return anyhow::Error::new(ExplainedRejection(format!

Error message

return anyhow::Error::new(ExplainedRejection(format!("Cannot {verb}: {rejected}")))

What it means

When explaining why changes were rejected (`explain_after_rollback`), the workspace and database must be loaded to produce a dependency report. If that load fails, the function falls back to a minimal `ExplainedRejection` containing only the verb and rejection count — the explanation is degraded but the original failure is never masked.

Solutions

  1. Treat the message as a count-only explanation and inspect the earlier root-cause error alongside it.
  2. Check for and clear stale locks on the project database / workspace files.
  3. Re-open or re-initialize the project in GitButler to rebuild the workspace state.
  4. Enable tracing output — the load failure is logged with `tracing::warn!(?load_err, ...)`; use that error to fix the underlying cause.
Defensive patterns

Strategy: fallback

Try / catch

// consume the ExplainedRejection but keep the degraded message
match op().await {
    Err(e) => {
        let msg = e.to_string();
        if msg.starts_with("Cannot ") { log::warn!("explanation degraded: {msg}"); }
        Err(e)
    }
    ok => ok,
}

Prevention

When it happens

Trigger: Calling the post-rollback explanation path when `ctx.workspace_and_db_with_perm` fails, e.g. the workspace state on disk is corrupt/locked, the database cannot be opened, or the read permission is unavailable at that moment.

Common situations: A crashed prior run left the workspace data in a bad state; the project database file is locked by another process; repository moved/deleted between the operation and the explanation; permission degradation during shutdown.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/e5d24f06d440825d. Report an issue: GitHub.

Appendix: 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 58e5313667)