zed-industries/zed · error
grammar name '{grammar_name}' must be written in snake_case:
Error message
grammar name '{grammar_name}' must be written in snake_case: {snake_cased_grammar_name} What it means
While compiling extension grammars, the builder converts each grammar name from extension.toml's [grammars] table with to_snake_case() and bails if the original name differs. Grammar keys must already be snake_case (lowercase words joined by underscores) because the key is used verbatim for the tree-sitter symbol export (tree_sitter_{grammar_name}) and cache directory naming.
Source
Thrown at crates/extension/src/extension_builder.rs:147
log::info!("compiling Rust extension {extension_dir:?}");
self.compile_rust_extension(extension_dir, extension_manifest, tx, &options)
.await
.context("compiling Rust extension")?;
log::info!("compiled Rust extension {extension_dir:?}");
Ok(())
}
.boxed()
});
let grammar_compilation_tasks = extension_manifest
.grammars
.iter()
.zip(clang_path.into_iter().flatten())
.map(|((grammar_name, grammar_metadata), clang_path_task)| {
async move {
let snake_cased_grammar_name = grammar_name.to_snake_case();
anyhow::ensure!(
grammar_name.as_ref() == snake_cased_grammar_name.as_str(),
"grammar name '{grammar_name}' must be \
written in snake_case: {snake_cased_grammar_name}"
);
log::info!("compiling grammar {grammar_name} for extension {extension_dir:?}");
let clang_path = clang_path_task.await.context("resolving clang path")?;
self.compile_grammar(
extension_dir,
grammar_name.as_ref(),
grammar_metadata,
&clang_path,
)
.await
.with_context(|| format!("compiling grammar '{grammar_name}'"))?;
log::info!("compiled grammar {grammar_name} for extension {extension_dir:?}");View on GitHub (pinned to 9d272b0363)
Solutions
- Rename the [grammars] key in extension.toml to the snake_cased name shown in the error message
- Use the bare grammar name (usually the language name, e.g. 'ruby', 'markdown-inline') that matches the tree-sitter grammar's declared name
- Re-run 'zed extension build' to confirm the manifest parses and the key round-trips through to_snake_case unchanged
Example fix
# before
[grammars]
"tree-sitter-my-lang" = { git = "https://github.com/me/tree-sitter-my-lang", rev = "..." }
# after
[grammars]
my_lang = { git = "https://github.com/me/tree-sitter-my-lang", rev = "..." } Defensive patterns
Strategy: validation
Validate before calling
// Lint manifest grammar keys before building
fn grammar_key_is_snake_case(key: &str) -> bool {
key == convert_case? // no crate? use a to_snake_case equivalent, e.g.:
}
// simplest: reject anything but [a-z0-9_]
fn grammar_key_is_snake_case(key: &str) -> bool {
key.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
} Prevention
- Use bare snake_case language names as [grammars] keys from the first commit
- Add a manifest lint to CI that rejects hyphens and capitals in grammar keys
- When in doubt, check the name shown in the error's expected snake_cased form
When it happens
Trigger: An extension.toml [grammars] entry whose key is not snake_case: hyphenated repo names like "tree-sitter-my-lang", CamelCase like "MyLang", or dotted/kebab names. The message tells you the expected form: {snake_cased_grammar_name}.
Common situations: Authors copying the GitHub repo name (tree-sitter-foo) or the grammar's CamelCase C symbol (TreeSitterFoo) as the manifest key instead of the plain snake_case grammar name Zed expects (e.g. "foo" or "tree_sitter_foo").
Related errors
- failed to compile {} parser with clang: {}
- grammar directory '{}' already exists, but is not a git clon
- failed to run `git init` in directory '{}'
- capability for process:exec {desired_command} {desired_args:
- extension dir {} is not an absolute path
AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-09-12).
Data as JSON: /api/errors/f8abae869a8ab6d4.
Report an issue: GitHub.