github/spec-kit · error · ValueError
Integration manifest at {path} must be a JSON object, got {t
Error message
Integration manifest at {path} must be a JSON object, got {type(data).__name__} What it means
IntegrationManifest.load() parsed the JSON successfully but the top-level value is not an object (e.g. a list or a bare string). The manifest schema requires a JSON object with keys like files, version, installed_at.
Source
Thrown at src/specify_cli/integrations/manifest.py:483
"""Load an existing manifest from disk.
Raises ``FileNotFoundError`` if the manifest does not exist.
"""
inst = cls(key, project_root, resolve_project_root=resolve_project_root)
path = inst.manifest_path
try:
data = json.loads(path.read_text(encoding="utf-8"))
except UnicodeDecodeError as exc:
raise ValueError(
f"Integration manifest at {path} is not valid UTF-8"
) from exc
except json.JSONDecodeError as exc:
raise ValueError(
f"Integration manifest at {path} contains invalid JSON"
) from exc
if not isinstance(data, dict):
raise ValueError(
f"Integration manifest at {path} must be a JSON object, "
f"got {type(data).__name__}"
)
files = data.get("files", {})
if not isinstance(files, dict) or not all(
isinstance(k, str) and isinstance(v, str) for k, v in files.items()
):
raise ValueError(
f"Integration manifest 'files' at {path} must be a "
"mapping of string paths to string hashes"
)
inst.version = data.get("version", "")
inst._installed_at = data.get("installed_at", "")
inst._files = files
recovered = data.get("recovered_files", [])View on GitHub (pinned to bf88c9f9a8)
Solutions
- Inspect the top-level type: python -c "import json;print(type(json.load(open('<path>'))))"
- Restore the object structure from git history, or delete and regenerate via specify integration install <key>
- Fix the script that rewrote the manifest to always emit an object
Example fix
# before
[".claude/commands/build.md"]
# after
{"files": {".claude/commands/build.md": "<sha256>"}, "integration": "claude", "version": ""} Defensive patterns
Strategy: type-guard
Validate before calling
import json
data = json.loads(manifest.manifest_path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
regenerate_manifest(key, root) Type guard
def is_manifest_object(data: object) -> bool:
return isinstance(data, dict) and isinstance(data.get("files", {}), dict) Try / catch
try:
IntegrationManifest.load(key, root)
except ValueError as exc:
if "must be a JSON object" in str(exc):
regenerate_manifest(key, root)
else:
raise Prevention
- Make scripts that transform manifests always emit objects
- jq-rewrite with '. as object' semantics, verify output shape
- Keep the manifest under git for easy restore
When it happens
Trigger: Manifest file whose root is [] , "text", 42, true, etc. — usually the result of a bad hand edit, a script writing the wrong structure, or a partial rewrite that left only a fragment value.
Common situations: Automation jq-transforming the manifest and emitting an array; manual truncation leaving only a value; tools that write JSONL into the file.
Related errors
- Integration manifest 'files' at {path} must be a mapping of
- Integration manifest 'recovered_files' at {path} must be a l
- auth.json must contain a 'providers' array
- providers[{i}]: must be a JSON object
- providers[{i}]: 'hosts' must be a non-empty array
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/81ea526c8d8d1d56.
Report an issue: GitHub.