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

  1. Rename the [grammars] key in extension.toml to the snake_cased name shown in the error message
  2. Use the bare grammar name (usually the language name, e.g. 'ruby', 'markdown-inline') that matches the tree-sitter grammar's declared name
  3. 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

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


AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-09-12). Data as JSON: /api/errors/f8abae869a8ab6d4. Report an issue: GitHub.