github/spec-kit · error · ValidationError

No extension.yml found in archive

Error message

No extension.yml found in archive

What it means

Thrown by ExtensionManager.install_from_zip after extracting an archive to a temp directory: neither the extraction root nor the single top-level subdirectory contains an extension.yml manifest. The installer only looks one level deep, so a manifest nested further down (or absent entirely) is treated as an invalid extension package.

Source

Thrown at src/specify_cli/extensions/__init__.py:2723

                archive_file=archive_file,
                source_name=source_name,
                content_type=content_type,
                error_type=ValidationError,
            )

            # Find extension directory (may be nested)
            extension_dir = temp_path
            manifest_path = extension_dir / "extension.yml"

            # Check if manifest is in a subdirectory
            if not manifest_path.exists():
                subdirs = [d for d in temp_path.iterdir() if d.is_dir()]
                if len(subdirs) == 1:
                    extension_dir = subdirs[0]
                    manifest_path = extension_dir / "extension.yml"

            if not manifest_path.exists():
                raise ValidationError("No extension.yml found in archive")

            # Install from extracted directory
            return self.install_from_directory(
                extension_dir, speckit_version, priority=priority, force=force
            )

    def _config_root_is_contained(self, specify_dir: Path) -> bool:
        """Report whether `.specify` is a real directory inside the project.

        Checked component by component so a symlink anywhere on the path is
        rejected before it becomes the containment root. A missing `.specify`
        is fine: scaffolding creates it under the project root.
        """
        try:
            root = self.project_root.resolve()
        except OSError:
            return False
        current = self.project_root

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Rebuild the archive so extension.yml sits at the root or inside exactly one top-level directory: `cd my-ext && zip -r ../my-ext.zip .`
  2. Inspect the archive layout first: `unzip -l my-ext.zip` or `tar -tzf my-ext.tar.gz` and confirm the manifest's depth
  3. If nesting is unavoidable, extract locally and install from the directory instead (install_from_directory), pointing at the folder that holds extension.yml
  4. If packaging via GitHub 'Source code' archives, add a release artifact or CI step that repacks with the manifest at the root

Example fix

# before: zip created from parent directory
zip -r my-ext.zip my-ext/          # root = my-ext/, extension.yml inside -> OK only if single dir

# problem: multiple dirs at root
zip -r bundle.zip ext/ docs/ tests/ # root has 3 dirs, manifest not found

# after: single top-level dir containing the manifest, or manifest at root
cd my-ext && zip -r ../my-ext.zip .
Defensive patterns

Strategy: validation

Validate before calling

import zipfile, tarfile
from pathlib import Path

def archive_has_root_manifest(path: Path) -> bool:
    """True if extension.yml is at archive root or its single top-level dir."""
    names = []
    if zipfile.is_zipfile(path):
        with zipfile.ZipFile(path) as z:
            names = z.namelist()
    elif tarfile.is_tarfile(path):
        with tarfile.open(path) as t:
            names = t.getnames()
    else:
        return False
    tops = {n.split('/')[0] for n in names if n.strip()}
    return 'extension.yml' in names or (
        len(tops) == 1 and f'{next(iter(tops))}/extension.yml' in names
    )

assert archive_has_root_manifest(Path('my-ext.zip'))

Try / catch

from specify_cli.extensions import ExtensionError

try:
    manager.install_from_zip(archive, speckit_version)
except ExtensionError as e:
    if 'No extension.yml found' in str(e):
        # repackage with manifest at root, or install from directory
        ...

Prevention

When it happens

Trigger: Calling install_from_zip (directly or via `specify extension install <path-or-url>` on a .zip/.tar.gz) where the archive has no extension.yml at its root and either has multiple top-level directories or a single subdir that also lacks extension.yml.

Common situations: Zipping the parent workspace folder instead of the extension package; an archive with multiple nested wrappers (dist/my-ext-v1.0/my-ext/extension.yml); downloading a source tarball from GitHub whose root is a repo folder plus .github, tests, etc.; forgetting to include the manifest at all.

Related errors


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