github/spec-kit · error · BundlerError

No README.md found in '{bundle_dir}'. Every bundle must ship

Error message

No README.md found in '{bundle_dir}'. Every bundle must ship a README.md describing it.

What it means

The bundle artifact contract requires a human-facing README.md next to bundle.yml; build_bundle() refuses to publish a bundle with no description. This is a deliberate quality gate, not an incidental file check — it fires even when bundle.yml itself is valid.

Source

Thrown at src/specify_cli/bundler/services/packager.py:46

@dataclass
class BuildResult:
    artifact_path: Path
    file_count: int


def build_bundle(
    bundle_dir: Path,
    output_dir: Path | None = None,
) -> BuildResult:
    bundle_dir = Path(bundle_dir).resolve()
    manifest_path = bundle_dir / "bundle.yml"
    if not manifest_path.exists():
        raise BundlerError(f"No bundle.yml found in '{bundle_dir}'.")

    # The artifact contract requires a human-facing README.md alongside the
    # manifest; refuse early rather than publish a bundle with no description.
    if not (bundle_dir / "README.md").exists():
        raise BundlerError(
            f"No README.md found in '{bundle_dir}'. Every bundle must ship a "
            "README.md describing it."
        )

    manifest = BundleManifest.from_file(manifest_path)
    report = validate_manifest(manifest)
    if not report.ok:
        raise BundlerError(
            "Refusing to build an invalid manifest. Run 'specify bundle validate' "
            "and fix:\n  - " + "\n  - ".join(report.errors)
        )

    out_dir = Path(output_dir).resolve() if output_dir else bundle_dir
    out_dir.mkdir(parents=True, exist_ok=True)
    artifact_name = f"{manifest.bundle.id}-{manifest.bundle.version}.zip"
    artifact_path = out_dir / artifact_name
    # Defense in depth: even though validate_manifest() rejects unsafe ids, make
    # sure a crafted id cannot push the artifact outside the output directory.

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Create <bundle_dir>/README.md describing what the bundle installs and why (exact name, top level of the bundle dir).
  2. Check for case or extension mismatches: readme.md/README.rst will not satisfy the check.
  3. Re-run `specify bundle build` after adding the file.

Example fix

echo '# My Bundle\nInstalls the X preset and Y extension.' > <bundle_dir>/README.md
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

assert (bundle_dir / "README.md").is_file(), "bundle needs a top-level README.md"
build_bundle(bundle_dir)

Try / catch

from specify_cli.bundler.core import BundlerError

try:
    build_bundle(bundle_dir)
except BundlerError as exc:
    if "README.md" in str(exc):
        (bundle_dir / "README.md").write_text("# Bundle\nTODO: describe.")
    raise

Prevention

When it happens

Trigger: Calling build_bundle() on a directory that has bundle.yml but no README.md — typically an author who wrote the manifest and components but never documented the bundle.

Common situations: New bundle authors; README saved as readme.md or README.rst (case/extension mismatch); README placed in a subdirectory instead of the bundle root.

Related errors


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