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

  1. Rebuild the zip so bundle.yml is at the archive root: cd my-bundle && zip ../bundle.zip bundle.yml ...
  2. Or unzip locally and install the directory / manifest path instead: specify bundle install ./my-bundle.
  3. 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

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


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/c70a528ccf2ea113. Report an issue: GitHub.