zed-industries/zed · error · anyhow::Error

grammar directory '{}' already exists, but is not a git clon

Error message

grammar directory '{}' already exists, but is not a git clone of '{}'

What it means

checkout_repo() reuses a cached grammar directory between builds. If the directory exists, it runs 'git --git-dir <dir>/.git remote get-url origin' (with GIT_CONFIG_GLOBAL=/dev/null) and requires the output to equal the manifest's git URL. A mismatch - or the git command failing because the directory is not a git clone at all - bails with this error, refusing to reuse the cache for a different repository.

Source

Thrown at crates/extension/src/extension_builder.rs:376

        Ok(())
    }

    async fn checkout_repo(&self, directory: &Path, url: &str, rev: &str) -> Result<()> {
        let git_dir = directory.join(".git");

        if directory.exists() {
            let remotes_output = util::command::new_command("git")
                .arg("--git-dir")
                .arg(&git_dir)
                .args(["remote", "get-url", "origin"])
                .env("GIT_CONFIG_GLOBAL", "/dev/null")
                .output()
                .await?;
            let has_remote = remotes_output.status.success()
                && String::from_utf8_lossy(&remotes_output.stdout).trim() == url;
            if !has_remote {
                bail!(
                    "grammar directory '{}' already exists, but is not a git clone of '{}'",
                    directory.display(),
                    url
                );
            }
        } else {
            fs::create_dir_all(directory).with_context(|| {
                format!("failed to create grammar directory {}", directory.display(),)
            })?;
            let init_output = util::command::new_command("git")
                .arg("init")
                .current_dir(directory)
                .output()
                .await?;
            if !init_output.status.success() {
                bail!(
                    "failed to run `git init` in directory '{}'",
                    directory.display()

View on GitHub (pinned to f4178619ac)

Solutions

  1. Delete the offending cached grammar directory under the extension builder's cache (the 'grammars' cache next to its work/cache dirs, e.g. ~/.cache/zed/extensions/...) so the next build re-clones from the new URL
  2. If the URL change was unintentional, revert extension.toml to the URL the cache was cloned from
  3. Pre-check which URL the cache holds: git --git-dir <cache>/.git remote get-url origin, and keep it in sync whenever you edit the grammar URL
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the cache before building
let expected_url = &grammar_meta.git;
let output = std::process::Command::new("git")
    .args(["--git-dir", cache_grammar_git_dir.to_str().unwrap(), "remote", "get-url", "origin"])
    .output()?;
let cache_matches = output.status.success()
    && String::from_utf8_lossy(&output.stdout).trim() == expected_url;
if !cache_matches {
    std::fs::remove_dir_all(cache_grammar_dir)?; // let the builder re-clone
}

Prevention

When it happens

Trigger: Building an extension whose grammar cache directory exists but its origin remote points elsewhere: the extension.toml grammar 'git' URL was changed between builds (repo moved, fork switched), the directory was created by something other than git, or .git is corrupt so 'remote get-url' exits non-zero (has_remote becomes false).

Common situations: Extension author renames the upstream repo or swaps a grammar for a fork; a previous build crashed midway leaving a non-git directory; the cache dir was manually populated or restored from a bad backup.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/2fbf0c76a82055f8. Report an issue: GitHub.