github/spec-kit · error · PresetValidationError

No preset.yml found in archive

Error message

No preset.yml found in archive

What it means

After safely extracting an archive, install_from_archive looks for preset.yml at the archive root, then — if there is exactly one subdirectory — inside that single subdirectory (the common 'repo zipped with a top-level folder' layout). If neither location has preset.yml, it aborts with PresetValidationError before delegating to install_from_directory.

Source

Thrown at src/specify_cli/presets/__init__.py:3761

            temp_path = Path(tmpdir)

            safe_extract_archive(
                archive_path,
                temp_path,
                error_type=PresetValidationError,
            )

            pack_dir = temp_path
            manifest_path = pack_dir / "preset.yml"

            if not manifest_path.exists():
                subdirs = [d for d in temp_path.iterdir() if d.is_dir()]
                if len(subdirs) == 1:
                    pack_dir = subdirs[0]
                    manifest_path = pack_dir / "preset.yml"

            if not manifest_path.exists():
                raise PresetValidationError(
                    "No preset.yml found in archive"
                )

            return self.install_from_directory(pack_dir, speckit_version, priority, force=force)

    def install_from_zip(
        self,
        zip_path: Path,
        speckit_version: str,
        priority: int = 10,
        force: bool = False,
    ) -> PresetManifest:
        """Backward-compatible wrapper for archive installation."""
        return self.install_from_archive(
            zip_path,
            speckit_version,
            priority,
            force=force,

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Repack the archive so preset.yml sits at the root, or at the root of exactly one top-level directory.
  2. Check the filename is exactly `preset.yml` (not .yaml).
  3. Verify contents first: `tar -tzf preset.tgz | head` or `unzip -l preset.zip` — if there are stray top-level files (__MACOSX, .DS_Store, README), remove them and repack.

Example fix

# before: archive layout
my-preset/
README.md          # second top-level entry breaks single-subdir detection
my-preset/preset.yml

# after
preset.yml         # at root of archive
scripts/
commands/
Defensive patterns

Strategy: validation

Validate before calling

import tarfile, zipfile

def archive_layout_ok(path) -> bool:
    names = []
    if zipfile.is_zipfile(path):
        names = [n for n in zipfile.ZipFile(path).namelist() if not n.startswith("__MACOSX")]
    else:
        names = tarfile.open(path).getnames()
    top = {n.split("/", 1)[0] for n in names if n.strip()}
    root_ok = any(n.split("/", 1)[0] == "preset.yml" or n == "preset.yml" for n in names)
    single_sub = len(top) == 1 and any(n.count("/") == 1 and n.endswith("/preset.yml") for n in names)
    return root_ok or single_sub

Try / catch

try:
    manager.install_from_archive(arch, ver)
except PresetValidationError as e:
    if "No preset.yml" in str(e):
        # repack with preset.yml at root (or single top-level dir) and retry
        ...

Prevention

When it happens

Trigger: Installing a .tar.gz/.zip that contains multiple top-level entries and no root preset.yml, a preset.yml with a different name (preset.yaml, manifest.yml), or nested two levels deep (e.g. repo-main/preset/preset.yml).

Common situations: Zipping a GitHub repo checkout that has both the preset folder and README/tests at the top level (two top-level entries breaks the single-subdir fallback), renaming preset.yml, or packaging the wrong directory.

Related errors


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