github/spec-kit · error · PresetValidationError

Invalid template name '{tmpl['name']}': must be lowercase al

Error message

Invalid template name '{tmpl['name']}': must be lowercase alphanumeric with hyphens only

What it means

Template entries whose type is not `command` (e.g. context, script, or other file templates) must have a `name` matching ^[a-z0-9-]+$ — lowercase letters, digits, and hyphens only, no dots. Dots are reserved for command dot-notation, so a dot in any other template type is rejected.

Source

Thrown at src/specify_cli/presets/__init__.py:482

                    f"must be one of {sorted(VALID_PRESET_STRATEGIES)}"
                )
            if tmpl["type"] == "script" and strategy not in VALID_SCRIPT_STRATEGIES:
                raise PresetValidationError(
                    f"Invalid strategy '{strategy}' for script: "
                    f"scripts only support {sorted(VALID_SCRIPT_STRATEGIES)}"
                )

            # Validate template name format
            if tmpl["type"] == "command":
                # Commands use dot notation (e.g. speckit.specify)
                if not re.match(r'^[a-z0-9.-]+$', tmpl["name"]):
                    raise PresetValidationError(
                        f"Invalid command name '{tmpl['name']}': "
                        "must be lowercase alphanumeric with hyphens and dots only"
                    )
            else:
                if not re.match(r'^[a-z0-9-]+$', tmpl["name"]):
                    raise PresetValidationError(
                        f"Invalid template name '{tmpl['name']}': "
                        "must be lowercase alphanumeric with hyphens only"
                    )

    @property
    def id(self) -> str:
        """Get preset ID."""
        return self.data["preset"]["id"]

    @property
    def name(self) -> str:
        """Get preset name."""
        return self.data["preset"]["name"]

    @property
    def version(self) -> str:
        """Get preset version."""
        return self.data["preset"]["version"]

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Rename the template to lowercase kebab-case without dots: `config-json`, `setup-script`.
  2. Remember the `file` field carries the actual output filename (which may contain dots) — only `name` is restricted.
  3. If the name is a command with dot notation, its `type` must be `command`.

Example fix

# before
  - name: eslint.config
    type: context
    file: eslint.config.js

# after
  - name: eslint-config
    type: context
    file: eslint.config.js
Defensive patterns

Strategy: validation

Validate before calling

import re
for t in yaml.safe_load(open("preset.yml")).get("templates", []):
    if t.get("type") != "command":
        assert re.fullmatch(r"[a-z0-9-]+", t["name"]), f"bad template name {t['name']!r} (no dots)"

Type guard

import re
def is_valid_template_name(name: str) -> bool:
    return bool(re.fullmatch(r"[a-z0-9-]+", name))

Try / catch

try:
    manager.install_from_directory(src, version)
except PresetValidationError as e:
    if "Invalid template name" in str(e):
        # dots are only allowed for type: command; rename to kebab-case
        ...

Prevention

When it happens

Trigger: A non-command template in preset.yml with a name like `config.json`, `my.template`, `Setup_Script`, or any uppercase/underscore/space character. Runs during PresetManifest validation at install.

Common situations: Naming a template after the file it produces (e.g. `eslint.config`), which incorrectly includes a dot; or reusing a command-style dotted name for a context/script template.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/cda4b09596194590. Report an issue: GitHub.