{"record":{"id":"c70a528ccf2ea113","repo":"github/spec-kit","slug":"artifact-candidate-does-not-contain-a-bundle-y","errorCode":null,"errorMessage":"Artifact '{candidate}' does not contain a bundle.yml.","messagePattern":"Artifact '(.+?)' does not contain a bundle\\.yml\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/commands/bundle/__init__.py","lineNumber":783,"sourceCode":"    if not candidate.exists():\n        return None\n\n    if candidate.is_dir():\n        manifest_path = candidate / \"bundle.yml\"\n        if not manifest_path.exists():\n            raise BundlerError(f\"No bundle.yml found in '{candidate}'.\")\n        return BundleManifest.from_file(manifest_path)\n\n    if candidate.suffix == \".zip\":\n        import yaml as _yaml\n\n        from ..._download_security import open_zip_bounded, read_zip_member_limited\n\n        with open_zip_bounded(candidate, error_type=BundlerError) as archive:\n            try:\n                archive.getinfo(\"bundle.yml\")\n            except KeyError as exc:\n                raise BundlerError(\n                    f\"Artifact '{candidate}' does not contain a bundle.yml.\"\n                ) from exc\n            raw = read_zip_member_limited(\n                archive,\n                \"bundle.yml\",\n                error_type=BundlerError,\n                label=\"bundle manifest\",\n            )\n        # The bounded-zip helpers above keep archive failures inside the\n        # BundlerError contract, but the manifest bytes need the same\n        # treatment as yamlio.load_yaml: decode as UTF-8 explicitly —\n        # feeding PyYAML the byte stream would let its Reader auto-detect\n        # a UTF-16 BOM and accept a manifest the directory and bundle.yml\n        # sources reject.\n        try:\n            text = raw.decode(\"utf-8\")\n        except UnicodeError as exc:\n            raise BundlerError(","sourceCodeStart":765,"sourceCodeEnd":801,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/commands/bundle/__init__.py#L765-L801","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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')."],"exampleFix":"# before\nzip -r bundle.zip my-bundle/   # archive contains my-bundle/bundle.yml\n\n# after\ncd my-bundle && zip ../bundle.zip bundle.yml","handlingStrategy":"validation","validationCode":"import zipfile\n\nwith zipfile.ZipFile(\"bundle.zip\") as zf:\n    names = set(zf.namelist())\n    if \"bundle.yml\" not in names:\n        raise SystemExit(f\"zip lacks root bundle.yml; members: {sorted(names)[:5]}\")","typeGuard":"def zip_has_root_manifest(path) -> bool:\n    import zipfile\n    with zipfile.ZipFile(path) as zf:\n        return \"bundle.yml\" in zf.namelist()","tryCatchPattern":"try:\n    manifest = _local_manifest_source(Path(\"bundle.zip\"))\nexcept BundlerError as exc:\n    if \"does not contain a bundle.yml\" in str(exc):\n        # rebuild zip with bundle.yml at root, or unzip and pass the directory\n        ...","preventionTips":["Build zips from inside the bundle directory so bundle.yml lands at the root.","Verify with unzip -l before distributing artifacts."],"tags":["bundler","zip","manifest","packaging"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}