github/spec-kit · error · ValidationError
Invalid {singular} 'file' {label}: {reason}
Error message
Invalid {singular} 'file' {label}: {reason} What it means
The 'file' value of a provides.templates/scripts entry failed the shared path-safety policy in relative_extension_path_violation() (src/specify_cli/_utils.py:21) — the identical policy applied to command files, keeping manifest validation and the runtime registrar guard from drifting. It rejects non-string/empty values, whitespace padding, backslashes, absolute/anchored paths (POSIX, Windows drive/UNC), '..' traversal, trailing directory slashes, and platform-reserved components. The message label is repr(file) for strings, otherwise the artifact name.
Source
Thrown at src/specify_cli/extensions/__init__.py:615
raise ValidationError(
f"Invalid {singular} name: expected a string, got {type(name).__name__}"
)
if not VALID_EXTENSION_ARTIFACT_NAME_PATTERN.match(name):
raise ValidationError(
f"Invalid {singular} name '{name}': "
"must be lowercase alphanumeric with hyphens only"
)
if name in seen_names:
raise ValidationError(
f"Duplicate {singular} name '{name}' in 'provides.{section}'"
)
seen_names.add(name)
file_value = entry["file"]
reason = relative_extension_path_violation(file_value)
if reason:
label = repr(file_value) if isinstance(file_value, str) else f"for {singular} '{name}'"
raise ValidationError(f"Invalid {singular} 'file' {label}: {reason}")
if "description" in entry and not isinstance(entry["description"], str):
raise ValidationError(
f"Invalid {singular} description for '{name}': expected a string"
)
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(View on GitHub (pinned to bf88c9f9a8)
Solutions
- Use a relative, forward-slash path inside the extension directory: "templates/plan.md".
- Remove '..', leading '/', drive letters, and trailing slashes; trim whitespace.
- Ship any shared file inside the extension directory instead of pointing outside it.
Example fix
// before
{ "name": "plan", "file": "../shared/templates/plan.md" }
// after
{ "name": "plan", "file": "templates/plan.md" } Defensive patterns
Strategy: validation
Validate before calling
from specify_cli._utils import relative_extension_path_violation
for section in ("templates", "scripts"):
for e in manifest.get("provides", {}).get(section, []):
if isinstance(e, dict):
reason = relative_extension_path_violation(e.get("file"))
assert reason is None, f"unsafe {section} file {e.get('file')!r}: {reason}" Type guard
def is_safe_artifact_file(entry: dict) -> bool:
from specify_cli._utils import relative_extension_path_violation
return relative_extension_path_violation(entry.get("file")) is None Try / catch
try:
ExtensionManifest.load(path)
except ValidationError as e:
if "'file'" in str(e) and "must be" in str(e):
# rewrite the path relative to the extension dir with forward slashes
... Prevention
- All extension files must live inside the extension directory.
- No absolute paths, '..', backslashes, or trailing '/'.
- Lint with relative_extension_path_violation() pre-install.
When it happens
Trigger: {"file": "/opt/ext/t.md"}, {"file": "../shared/t.md"}, {"file": "templates\\t.md"}, {"file": "templates/"}, or a non-string file value. Raised by the artifact validator right after the duplicate-name check.
Common situations: Absolute paths pasted from a dev machine; Windows separators; referencing a shared directory outside the extension with '../'; template generators emitting directory paths.
Related errors
- Invalid command 'file' {label}: {reason}
- Invalid alias {alias!r} for command '{cmd['name']}': {alias_
- Output path {candidate!r} escapes directory {base!r}
- Invalid command name {cmd_name!r}: {name_reason}
- Invalid command alias {alias!r}: {alias_reason}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/4f798833ab7614c7.
Report an issue: GitHub.