github/spec-kit · error · ValidationError
Invalid command 'file' {label}: {reason}
Error message
Invalid command 'file' {label}: {reason} What it means
The 'file' field of a provides.commands entry failed the shared path-safety policy in relative_extension_path_violation() (src/specify_cli/_utils.py:21). That single policy is reused by both manifest-load validation and the runtime CommandRegistrar guard so the two cannot drift. It rejects non-string/empty values, leading/trailing whitespace, backslash separators, absolute/anchored paths (POSIX /abs, Windows C:\ or C:foo, UNC), '..' traversal, directory-suffixed values, and platform-reserved/invalid components. The label in the message is the repr of the offending string, or the command name when the value is not even a string.
Source
Thrown at src/specify_cli/extensions/__init__.py:471
# relative_extension_path_violation() below already rejects a
# non-string value.
if not isinstance(cmd["name"], str):
raise ValidationError(
f"Invalid command name: expected a string, "
f"got {type(cmd['name']).__name__}"
)
# Validate the 'file' field at manifest-load time using the single
# shared policy in relative_extension_path_violation(), so manifest
# validation cannot drift from the runtime registrar guard. This is
# defense-in-depth: the command/skill/preset readers also contain
# the resolved path, but rejecting an unsafe value here surfaces a
# clear error instead of silently skipping the command.
cmd_file = cmd["file"]
reason = relative_extension_path_violation(cmd_file)
if reason:
label = repr(cmd_file) if isinstance(cmd_file, str) else f"for command '{cmd.get('name')}'"
raise ValidationError(f"Invalid command 'file' {label}: {reason}")
# Validate command name format
if not EXTENSION_COMMAND_NAME_PATTERN.match(cmd["name"]):
corrected = self._try_correct_command_name(cmd["name"], ext["id"])
if corrected:
self.warnings.append(
f"Command name '{cmd['name']}' does not follow the required pattern "
f"'speckit.{{extension}}.{{command}}'. Registering as '{corrected}'. "
f"The extension author should update the manifest to use this name."
)
rename_map[cmd["name"]] = corrected
cmd["name"] = corrected
else:
raise ValidationError(
f"Invalid command name '{cmd['name']}': "
"must follow pattern 'speckit.{extension}.{command}'"
)
View on GitHub (pinned to bf88c9f9a8)
Solutions
- Change the file value to a relative, forward-slash path inside the extension directory, e.g. "commands/build.md".
- Remove leading/trailing whitespace and any '..' segments, drive letters, or leading slashes.
- Ensure the value is a non-empty string (not null/int) and names a file, not a directory (no trailing '/').
Example fix
// before
{ "name": "speckit.myext.build", "file": "/home/me/ext/commands/build.md" }
// after
{ "name": "speckit.myext.build", "file": "commands/build.md" } Defensive patterns
Strategy: validation
Validate before calling
from specify_cli._utils import relative_extension_path_violation
for cmd in manifest.get("provides", {}).get("commands", []):
reason = relative_extension_path_violation(cmd.get("file"))
if reason:
raise SystemExit(f"bad file for {cmd.get('name')}: {reason}") Type guard
def is_safe_relative_file(value: object) -> bool:
from specify_cli._utils import relative_extension_path_violation
return relative_extension_path_violation(value) is None Try / catch
try:
ExtensionManifest.load(path)
except ValidationError as e:
if "Invalid command 'file'" in str(e):
# rewrite file to a relative forward-slash path inside the extension dir
... Prevention
- Never put absolute paths or '..' in a manifest file field.
- Use forward slashes only; trim whitespace; no trailing '/'.
- Run relative_extension_path_violation() as a pre-install lint step.
When it happens
Trigger: A command entry with "file": "/etc/commands/x.md", "file": "..\\..\\x.md", "file": "commands/" (directory suffix), "file": " commands/x.md " (whitespace), or a non-string file value such as null or 3. Any of these hit ExtensionManifest._validate() during extension install/load.
Common situations: Authors copying absolute paths from local dev machines into the manifest; Windows authors using backslashes; manifests written by hand with trailing slashes; thinking 'file' can point outside the extension directory. Because the policy is defense-in-depth, even values the registrar might have skipped now fail loudly at load time.
Related errors
- Invalid {singular} '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/f74f64caad83e30b.
Report an issue: GitHub.