github/spec-kit · error · ValidationError
Could not read manifest {path}: {e}
Error message
Could not read manifest {path}: {e} What it means
Catch-all for OS-level read failures of the extension manifest other than missing-file and decode errors: the open/read raised OSError (e.g. permission denied, I/O error). The underlying errno message is embedded in the ValidationError.
Source
Thrown at src/specify_cli/extensions/__init__.py:255
self.warnings: List[str] = []
self.data = self._load_yaml(manifest_path)
self._validate()
def _load_yaml(self, path: Path) -> dict:
"""Load YAML file safely."""
try:
with open(path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
except yaml.YAMLError as e:
raise ValidationError(f"Invalid YAML in {path}: {e}")
except FileNotFoundError:
raise ValidationError(f"Manifest not found: {path}")
except UnicodeDecodeError as e:
raise ValidationError(
f"Manifest is not valid UTF-8: {path} ({e.reason} at byte {e.start})"
)
except OSError as e:
raise ValidationError(f"Could not read manifest {path}: {e}")
if not isinstance(data, dict):
raise ValidationError(
f"Manifest must be a YAML mapping, got {type(data).__name__}: {path}"
)
return data
def _validate(self):
"""Validate manifest structure and required fields."""
# Check required top-level fields
for field in self.REQUIRED_FIELDS:
if field not in self.data:
raise ValidationError(f"Missing required field: {field}")
# Validate schema version
if self.data["schema_version"] != self.SCHEMA_VERSION:
raise ValidationError(
f"Unsupported schema version: {self.data['schema_version']} "
f"(expected {self.SCHEMA_VERSION})"View on GitHub (pinned to bf88c9f9a8)
Solutions
- Read the embedded errno text — 'Permission denied' means fix modes/ownership (`chmod 644`, `chown`); 'Is a directory' means the path is wrong.
- Ensure the user running specify has read access to every component of the path.
- In containers, check volume mount permissions and uid mapping.
Example fix
# before $ ls -l .specify/extensions/my-ext/extension.yaml -rw------- 1 root root ... extension.yaml # after $ chmod 644 .specify/extensions/my-ext/extension.yaml
Defensive patterns
Strategy: try-catch
Validate before calling
import os
if not os.access(manifest_path, os.R_OK):
raise SystemExit(f"no read permission for {manifest_path}") Try / catch
except ValidationError as e:
if "Could not read manifest" in str(e):
diagnose_errno(e) # Permission denied -> chmod/chown; Is a directory -> wrong path Prevention
- Keep manifests mode 644 owned by the invoking user.
- In containers, align uid/gid between host and container for mounted extension dirs.
When it happens
Trigger: The manifest exists and decodes but the process cannot read it — EACCES on restrictive file modes, a directory instead of a file (IsADirectoryError is an OSError), or a failing disk/NFS mount.
Common situations: Manifest chmod 600 owned by another user; files extracted from an archive that lost permissions; running the CLI as a different user or inside a container with mismatched uid; the manifest path is actually a directory.
Related errors
- No extension.yml found in {source_path}
- Could not safely create download file: {exc}
- No bundle.yml found in '{bundle_dir}'.
- No bundle.yml found in '{candidate}'.
- No bundle.yml found at '{target}'.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/7225349877f12fdd.
Report an issue: GitHub.