{"record":{"id":"139b5f67a1429dee","repo":"github/spec-kit","slug":"invalid-runtimes-for-script-name-expected-a-l","errorCode":null,"errorMessage":"Invalid runtimes for script '{name}': expected a list of strings","messagePattern":"Invalid runtimes for script '(.+?)': expected a list of strings","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":633,"sourceCode":"                raise ValidationError(f\"Invalid {singular} 'file' {label}: {reason}\")\n\n            if \"description\" in entry and not isinstance(entry[\"description\"], str):\n                raise ValidationError(\n                    f\"Invalid {singular} description for '{name}': expected a string\"\n                )\n\n            if \"strategy\" in entry:\n                raise ValidationError(\n                    f\"Invalid {singular} entry '{name}': 'strategy' is not authorable for \"\n                    \"extension-provided artifacts, which always use 'replace' semantics\"\n                )\n\n            if section == \"scripts\" and \"runtimes\" in entry:\n                runtimes = entry[\"runtimes\"]\n                if not isinstance(runtimes, list) or not all(\n                    isinstance(r, str) for r in runtimes\n                ):\n                    raise ValidationError(\n                        f\"Invalid runtimes for script '{name}': expected a list of strings\"\n                    )\n                invalid = sorted(set(runtimes) - VALID_SCRIPT_RUNTIMES)\n                if invalid:\n                    raise ValidationError(\n                        f\"Invalid runtimes {invalid} for script '{name}': \"\n                        f\"must be one of {sorted(VALID_SCRIPT_RUNTIMES)}\"\n                    )\n\n    @staticmethod\n    def _try_correct_command_name(name: str, ext_id: str) -> Optional[str]:\n        \"\"\"Try to auto-correct a non-conforming command name to the required pattern.\n\n        Handles the two legacy formats used by community extensions:\n          - 'speckit.command'  → 'speckit.{ext_id}.command'\n          - '{ext_id}.command' → 'speckit.{ext_id}.command'\n\n        The 'X.Y' form is only corrected when X matches ext_id to ensure the","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L615-L651","documentation":"A provides.scripts entry declared 'runtimes' that is not a list of strings (e.g. a bare string, a dict, or a list containing non-strings). The runtimes field declares which interpreter variants a script ships (bash/powershell/python); only its container shape is checked here — element values are checked next against VALID_SCRIPT_RUNTIMES.","triggerScenarios":"\"runtimes\": \"bash\" (bare string), \"runtimes\": {\"sh\": 1}, or \"runtimes\": [\"bash\", 2]. Raised by the isinstance/all() check in the scripts-only branch of the artifact validator.","commonSituations":"Shorthand single-runtime declaration; YAML flow syntax collapsing a one-element list to a scalar; generated manifests emitting an enum object.","solutions":["Wrap runtimes in a list of strings: \"runtimes\": [\"bash\", \"python\"].","Drop the key if you don't need to declare runtimes explicitly.","Check the neighboring element check: after fixing shape, values must be from {bash, powershell, python}."],"exampleFix":"// before\n\"runtimes\": \"bash\"\n// after\n\"runtimes\": [\"bash\"]","handlingStrategy":"type-guard","validationCode":"for e in manifest.get(\"provides\", {}).get(\"scripts\", []):\n    if isinstance(e, dict) and \"runtimes\" in e:\n        r = e[\"runtimes\"]\n        assert isinstance(r, list) and all(isinstance(x, str) for x in r), f\"bad runtimes for {e.get('name')}: {r!r}\"","typeGuard":"def is_str_list_runtimes(entry: dict) -> bool:\n    r = entry.get(\"runtimes\")\n    return r is None or (isinstance(r, list) and all(isinstance(x, str) for x in r))","tryCatchPattern":"try:\n    ExtensionManifest.load(path)\nexcept ValidationError as e:\n    if \"runtimes\" in str(e) and \"list of strings\" in str(e):\n        # wrap runtimes in a list / quote elements, then retry\n        ...","preventionTips":["Always emit runtimes as a JSON array of strings.","Omit the key when not declaring runtimes.","Remember values must then be bash/powershell/python."],"tags":["extensions","manifest","scripts","validation","type-error"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}