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
- 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
- If the URL change was unintentional, revert extension.toml to the URL the cache was cloned from
- 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
- Whenever you change a grammar 'git' URL in extension.toml, delete that grammar's cache dir in the same change
- Never hand-copy directories into the grammar cache; let the builder clone them
- Treat this error as a cache/manifest mismatch, not a source problem
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
- failed to run `git init` in directory '{}'
- failed to compile {} parser with clang: {}
- grammar name '{grammar_name}' must be written in snake_case:
- extension dir {} is not an absolute path
- capability for process:exec {desired_command} {desired_args:
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/2fbf0c76a82055f8.
Report an issue: GitHub.