github/spec-kit · error · BundlerError

'{candidate}' is not a recognised bundle source (.zip artifa

Error message

'{candidate}' is not a recognised bundle source (.zip artifact, bundle directory, or bundle.yml).

What it means

_local_manifest_source dispatches on the argument's shape: .zip file, existing directory, or a file named bundle.yml / with .yml/.yaml suffix. The path existed but matched none of these (not a directory, not .zip, not a YAML file), so it is rejected as an unrecognised bundle source. Raising (instead of returning None) prevents the argument from falling through to catalog-by-id resolution, which would silently do the wrong thing.

Source

Thrown at src/specify_cli/commands/bundle/__init__.py:820

                f"Could not read bundle.yml inside '{candidate}': {exc}"
            ) from exc
        try:
            data = _yaml.safe_load(text)
        except _yaml.YAMLError as exc:
            # The sibling directory/bundle.yml branches reach YAML through
            # load_yaml(), which turns a parse failure into a BundlerError. This
            # branch parses inline, so without this it raises a raw YAMLError --
            # neither a ValueError nor an OSError -- which escapes
            # bundle_install()'s `except BundlerError` as a traceback.
            raise BundlerError(
                f"Invalid YAML in bundle.yml inside '{candidate}': {exc}"
            ) from exc
        return BundleManifest.from_dict(data)

    if candidate.name == "bundle.yml" or candidate.suffix in (".yml", ".yaml"):
        return BundleManifest.from_file(candidate)

    raise BundlerError(
        f"'{candidate}' is not a recognised bundle source (.zip artifact, bundle "
        "directory, or bundle.yml)."
    )


def _resolve_manifest_path(path: Path | None) -> Path:
    target = (path or Path.cwd()).resolve()
    if target.is_dir():
        target = target / "bundle.yml"
    if not target.exists():
        raise BundlerError(f"No bundle.yml found at '{target}'.")
    return target


def _download_manifest(resolved, *, offline: bool):
    """Resolve a bundle's manifest from its catalog ``download_url``.

    Catalog ``download_url``s are HTTPS-only (``http`` allowed for localhost),

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Unpack the archive and pass the bundle directory or bundle.yml path instead.
  2. If you meant a catalog id, remove the path-looking argument (a non-existent path returns None and falls back to catalog resolution).
  3. Produce a .zip artifact or a directory with bundle.yml as the installable formats.

Example fix

# before
specify bundle install ./my-bundle.tar.gz

# after
tar xzf my-bundle.tar.gz
specify bundle install ./my-bundle
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

candidate = Path(arg).expanduser()
recognized = (
    candidate.is_dir()
    or candidate.suffix == ".zip"
    or candidate.name == "bundle.yml"
    or candidate.suffix in (".yml", ".yaml")
)
if candidate.exists() and not recognized:
    raise SystemExit(f"Unsupported bundle source type: {candidate.suffix or 'no-extension'}")

Type guard

def is_recognized_source(path: Path) -> bool:
    return (
        path.is_dir()
        or path.suffix == ".zip"
        or path.name == "bundle.yml"
        or path.suffix in (".yml", ".yaml")
    )

Try / catch

try:
    manifest = _local_manifest_source(arg)
except BundlerError as exc:
    if "not a recognised bundle source" in str(exc):
        # unpack and pass the bundle directory / bundle.yml instead
        ...

Prevention

When it happens

Trigger: specify bundle install ./bundle.tar.gz, ./bundle.json, or any existing non-YAML, non-zip file.

Common situations: Bundles distributed as .tar.gz; a leftover .txt/.json file at the expected path; downloading the wrong artifact format.

Related errors


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