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
- Map short names to canonical ones: sh→bash, ps→powershell, py→python.
- Remove runtime identifiers the system does not support (e.g. node).
- 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
- Canonical runtime names are bash, powershell, python — not sh/ps/py.
- The allowed set is printed in the error message; align exactly.
- Lint runtimes values against VALID_SCRIPT_RUNTIMES in CI.
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
- Each entry in 'provides.{section}' must be a mapping
- Invalid runtimes for script '{name}': expected a list of str
- Manifest must be a YAML mapping, got {type(data).__name__}:
- Missing required field: {field}
- Invalid extension: expected a mapping, got {type(ext).__name
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/7dfe95f7461d3554.
Report an issue: GitHub.