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
- Repack the archive so preset.yml sits at the root, or at the root of exactly one top-level directory.
- Check the filename is exactly `preset.yml` (not .yaml).
- 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
- Put preset.yml at the archive root, or pack the preset directory itself so there is exactly one top-level folder.
- Name the file exactly preset.yml, not .yaml.
- Inspect archives before shipping: tar -tzf / unzip -l; exclude __MACOSX and stray top-level files.
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
- Priority must be a positive integer (1 or higher)
- Preset '{manifest.id}' is already installed. Use 'specify pr
- Failed to parse preset manifest {manifest_path}: {exc}
- Wrap layer {path} is missing {placeholder}
- Unknown template composition strategy '{strategy}' in {path}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/75776e3e865c8425.
Report an issue: GitHub.