zed-industries/zed · error

expected commit object, got {object_type}

Error message

expected commit object, got {object_type}

What it means

cat-file --batch resolved the requested SHA, but the object's type is not `commit` — it is a blob, tree, or annotated tag. The commit-loading path only accepts commit objects, so it refuses with the actual type name in the message.

Source

Thrown at crates/git/src/repository.rs:3583

            ))
        }
        .boxed()
    }

    fn commit_data_reader(&self) -> Result<CommitDataReader> {
        let git_binary = self.git_binary();

        let (request_tx, request_rx) = async_channel::bounded::<CommitDataRequest>(64);

        let task = self.executor.spawn(async move {
            if let Err(error) = run_commit_data_reader(git_binary, request_rx).await {
                log::error!("commit data reader failed: {error:?}");
            }
        });

        Ok(CommitDataReader {
            request_tx,
            _task: task,
        })
    }

    fn set_trusted(&self, trusted: bool) {
        self.is_trusted
            .store(trusted, std::sync::atomic::Ordering::Release);
    }

    fn is_trusted(&self) -> bool {
        self.is_trusted.load(std::sync::atomic::Ordering::Acquire)
    }
}

async fn run_commit_data_reader(
    git: GitBinary,
    request_rx: async_channel::Receiver<CommitDataRequest>,
) -> Result<()> {
    let mut process = git

View on GitHub (pinned to 5a9b9558db)

Solutions

  1. Dereference to a commit before loading: resolve with `git rev-parse <spec>^{commit}` and use the resulting SHA.
  2. Check the type first: `git cat-file -t <oid>` and skip non-commit objects.
  3. When capturing SHAs to pass around, always capture commit OIDs (from `rev-parse HEAD`-style calls, never tree or blob OIDs).
  4. Treat tag objects as expected input and peel them (`^{}` / `^{commit}`) rather than erroring.

Example fix

// before
let commit_sha = tree_oid_captured_from_ls_tree;
// after
let commit_sha = git
    .run(&["rev-parse", &format!("{spec}^{{commit}}")])
    .await?
    .trim()
    .to_string();
Defensive patterns

Strategy: type-guard

Validate before calling

// peel any spec to a commit OID before the commit-loading call
let commit_sha = git
    .run(&["rev-parse", &format!("{spec}^{{commit}}")])
    .await?;
let commit_sha = commit_sha.trim();

Type guard

async fn resolves_to_commit(git: &GitBinary, spec: &str) -> bool {
    git.run(&["cat-file", "-t", spec]).await
        .map(|t| t.trim() == "commit")
        .unwrap_or(false)
}

Try / catch

match load_commits(&shas).await {
    Err(e) if e.to_string().contains("expected commit object") => {
        // peel with ^{commit} and retry, or drop the non-commit OIDs
    }
    other => other?,
}

Prevention

When it happens

Trigger: Passing a blob OID (for example a file blob SHA captured from blame or the index) or a tree OID (from `git ls-tree` / `rev-parse HEAD^{tree}`) where a commit SHA is expected; passing an annotated-tag OID without dereferencing it to the commit it points at.

Common situations: OIDs collected from mixed git commands fed into a commit-metadata lookup; short SHAs resolving to an unexpected object in tag-heavy repositories; storing `git rev-parse <tag>` output (tag object) instead of `<tag>^{commit}`.

Related errors


AI-assisted analysis of zed-industries/zed@5a9b9558db (2026-08-20). Data as JSON: /api/errors/5131d1ea289a4145. Report an issue: GitHub.