github/spec-kit · error · BundlerError
'integration' must be a mapping when present.
Error message
'integration' must be a mapping when present.
What it means
Raised by BundleManifest.from_dict when an optional 'integration' key is present but is not a mapping. Previously a present-but-non-mapping value (e.g. the bare string 'copilot') was silently dropped, leaving the bundle wrongly integration-agnostic; the guard now rejects it, mirroring the requires/provides guards.
Source
Thrown at src/specify_cli/bundler/models/manifest.py:131
requires_raw = data.get("requires")
if requires_raw is None:
requires_raw = {}
elif not isinstance(requires_raw, dict):
raise BundlerError("'requires' must be a mapping when present.")
requires = Requires(
speckit_version=_text(requires_raw.get("speckit_version")),
tools=_parse_str_list(requires_raw.get("tools"), "requires.tools"),
mcp=_parse_str_list(requires_raw.get("mcp"), "requires.mcp"),
)
integration = None
integration_raw = data.get("integration")
# Mirror the requires/provides guards above: a present-but-non-mapping
# 'integration' (e.g. a bare string "copilot") was silently dropped,
# leaving the bundle wrongly integration-agnostic. Reject it instead.
if integration_raw is not None and not isinstance(integration_raw, dict):
raise BundlerError("'integration' must be a mapping when present.")
if isinstance(integration_raw, dict) and integration_raw.get("id"):
integration = IntegrationRef(id=str(integration_raw["id"]).strip())
provides = data.get("provides")
if provides is None:
provides = {}
elif not isinstance(provides, dict):
raise BundlerError("'provides' must be a mapping when present.")
tags_raw = data.get("tags")
if tags_raw is None:
tags_raw = []
else:
tags_raw = _parse_str_list(tags_raw, "tags")
manifest = cls(
schema_version=schema_version,
bundle=meta,View on GitHub (pinned to bf88c9f9a8)
Solutions
- Wrap the value in a mapping with an 'id' key: 'integration: {id: copilot}'.
- Remove the 'integration' key if the bundle is intentionally integration-agnostic.
Example fix
# before (bundle.yml) integration: copilot # after integration: id: copilot
Defensive patterns
Strategy: type-guard
Validate before calling
def integration_shape_ok(data: dict) -> bool:
i = data.get("integration")
return i is None or isinstance(i, dict) Type guard
def is_integration_ref(raw: object) -> bool:
return raw is None or (isinstance(raw, dict) and "id" in raw) Try / catch
try:
manifest = BundleManifest.from_file(p)
except BundlerError as e:
if "'integration' must be a mapping" in str(e):
# rewrite 'integration: copilot' as 'integration: {id: copilot}'
... Prevention
- Always nest integration identity: 'integration:\n id: <key>'.
- A bare string used to be silently dropped — never rely on that old behavior.
- Omit 'integration' for integration-agnostic bundles.
When it happens
Trigger: A manifest contains 'integration: copilot' (a string) instead of 'integration: {id: copilot}'. BundleManifest.from_dict sees integration_raw is not None and not a dict and raises.
Common situations: Naturally writing the integration name as a bare scalar — the most intuitive shorthand and the exact bug this guard closes; copying integration config from a different schema that uses strings.
Related errors
- Manifest must be a YAML mapping at the top level.
- Manifest is missing the required 'bundle' mapping.
- 'requires' must be a mapping when present.
- 'provides' must be a mapping when present.
- '{field_name}' must be a list of strings when present.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/6a3fbf8ccdd2357c.
Report an issue: GitHub.