github/spec-kit · error · BundlerError
Artifact '{candidate}' does not contain a bundle.yml.
Error message
Artifact '{candidate}' does not contain a bundle.yml. What it means
The .zip branch of _local_manifest_source: the archive was opened safely (open_zip_bounded) but contains no top-level 'bundle.yml' member (zipfile.getinfo raised KeyError). Every zip bundle must carry bundle.yml at the archive root; the error is wrapped to keep the BundlerError contract.
Source
Thrown at src/specify_cli/commands/bundle/__init__.py:783
if not candidate.exists():
return None
if candidate.is_dir():
manifest_path = candidate / "bundle.yml"
if not manifest_path.exists():
raise BundlerError(f"No bundle.yml found in '{candidate}'.")
return BundleManifest.from_file(manifest_path)
if candidate.suffix == ".zip":
import yaml as _yaml
from ..._download_security import open_zip_bounded, read_zip_member_limited
with open_zip_bounded(candidate, error_type=BundlerError) as archive:
try:
archive.getinfo("bundle.yml")
except KeyError as exc:
raise BundlerError(
f"Artifact '{candidate}' does not contain a bundle.yml."
) from exc
raw = read_zip_member_limited(
archive,
"bundle.yml",
error_type=BundlerError,
label="bundle manifest",
)
# The bounded-zip helpers above keep archive failures inside the
# BundlerError contract, but the manifest bytes need the same
# treatment as yamlio.load_yaml: decode as UTF-8 explicitly —
# feeding PyYAML the byte stream would let its Reader auto-detect
# a UTF-16 BOM and accept a manifest the directory and bundle.yml
# sources reject.
try:
text = raw.decode("utf-8")
except UnicodeError as exc:
raise BundlerError(View on GitHub (pinned to bf88c9f9a8)
Solutions
- Rebuild the zip so bundle.yml is at the archive root: cd my-bundle && zip ../bundle.zip bundle.yml ...
- Or unzip locally and install the directory / manifest path instead: specify bundle install ./my-bundle.
- Verify with: unzip -l bundle.zip | grep bundle.yml (must show 'bundle.yml', not 'dir/bundle.yml').
Example fix
# before zip -r bundle.zip my-bundle/ # archive contains my-bundle/bundle.yml # after cd my-bundle && zip ../bundle.zip bundle.yml
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
with zipfile.ZipFile("bundle.zip") as zf:
names = set(zf.namelist())
if "bundle.yml" not in names:
raise SystemExit(f"zip lacks root bundle.yml; members: {sorted(names)[:5]}") Type guard
def zip_has_root_manifest(path) -> bool:
import zipfile
with zipfile.ZipFile(path) as zf:
return "bundle.yml" in zf.namelist() Try / catch
try:
manifest = _local_manifest_source(Path("bundle.zip"))
except BundlerError as exc:
if "does not contain a bundle.yml" in str(exc):
# rebuild zip with bundle.yml at root, or unzip and pass the directory
... Prevention
- Build zips from inside the bundle directory so bundle.yml lands at the root.
- Verify with unzip -l before distributing artifacts.
When it happens
Trigger: specify bundle install ./bundle.zip where bundle.yml sits in a subfolder (e.g. my-bundle/bundle.yml), is misnamed, or the zip was built from the wrong directory.
Common situations: Zipping a parent folder (zip -r bundle.zip my-bundle/) which nests the manifest one level deep; artifact built by CI packaging the repo root; case mismatch (Bundle.yml).
Related errors
- No extension.yml found in archive
- Manifest must be a YAML mapping at the top level.
- Manifest is missing the required 'bundle' mapping.
- 'requires' must be a mapping when present.
- 'integration' must be a mapping when present.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/c70a528ccf2ea113.
Report an issue: GitHub.