github/spec-kit · error · ValidationError

Invalid runtimes {invalid} for script '{name}': must be one

Error message

Invalid runtimes {invalid} for script '{name}': must be one of {sorted(VALID_SCRIPT_RUNTIMES)}

What it means

A provides.scripts entry declared 'runtimes' values outside VALID_SCRIPT_RUNTIMES = {"bash", "powershell", "python"} (extensions/__init__.py:69). The invalid values are collected (deduplicated, sorted) into the message along with the allowed set. These three map to Spec Kit's script-type contract (--script sh|ps|py); older names like 'sh', 'ps', 'shell', or 'pwsh' are not accepted.

Source

Thrown at src/specify_cli/extensions/__init__.py:638

                )

            if "strategy" in entry:
                raise ValidationError(
                    f"Invalid {singular} entry '{name}': 'strategy' is not authorable for "
                    "extension-provided artifacts, which always use 'replace' semantics"
                )

            if section == "scripts" and "runtimes" in entry:
                runtimes = entry["runtimes"]
                if not isinstance(runtimes, list) or not all(
                    isinstance(r, str) for r in runtimes
                ):
                    raise ValidationError(
                        f"Invalid runtimes for script '{name}': expected a list of strings"
                    )
                invalid = sorted(set(runtimes) - VALID_SCRIPT_RUNTIMES)
                if invalid:
                    raise ValidationError(
                        f"Invalid runtimes {invalid} for script '{name}': "
                        f"must be one of {sorted(VALID_SCRIPT_RUNTIMES)}"
                    )

    @staticmethod
    def _try_correct_command_name(name: str, ext_id: str) -> Optional[str]:
        """Try to auto-correct a non-conforming command name to the required pattern.

        Handles the two legacy formats used by community extensions:
          - 'speckit.command'  → 'speckit.{ext_id}.command'
          - '{ext_id}.command' → 'speckit.{ext_id}.command'

        The 'X.Y' form is only corrected when X matches ext_id to ensure the
        result passes the install-time namespace check. Any other prefix is
        uncorrectable and will produce a ValidationError at the call site.

        Returns the corrected name, or None if no safe correction is possible.
        """

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Map short names to canonical ones: sh→bash, ps→powershell, py→python.
  2. Remove runtime identifiers the system does not support (e.g. node).
  3. Re-check with the allowed set printed in the error message: ['bash', 'powershell', 'python'].

Example fix

// before
"runtimes": ["sh", "py"]
// after
"runtimes": ["bash", "python"]
Defensive patterns

Strategy: validation

Validate before calling

VALID = {"bash", "powershell", "python"}
for e in manifest.get("provides", {}).get("scripts", []):
    if isinstance(e, dict) and isinstance(e.get("runtimes"), list):
        invalid = set(e["runtimes"]) - VALID
        assert not invalid, f"invalid runtimes {sorted(invalid)} for {e.get('name')}"

Type guard

def runtimes_all_valid(entry: dict) -> bool:
    VALID = {"bash", "powershell", "python"}
    r = entry.get("runtimes")
    return r is None or (isinstance(r, list) and set(r) <= VALID)

Try / catch

try:
    ExtensionManifest.load(path)
except ValidationError as e:
    if "must be one of" in str(e):
        # map sh->bash, ps->powershell, py->python; drop unsupported values
        ...

Prevention

When it happens

Trigger: "runtimes": ["sh", "py"] or ["powershell", "node"]. Raised by the set-difference check after the list-of-strings shape check in the scripts branch.

Common situations: Using the --script flag short names (sh/ps/py) as runtime values; declaring 'node' or 'ruby' for a helper script; assuming PowerShell's executable name 'pwsh' is the identifier.

Related errors


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