{"record":{"id":"75776e3e865c8425","repo":"github/spec-kit","slug":"no-preset-yml-found-in-archive","errorCode":null,"errorMessage":"No preset.yml found in archive","messagePattern":"No preset\\.yml found in archive","errorType":"validation","errorClass":"PresetValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":3761,"sourceCode":"            temp_path = Path(tmpdir)\n\n            safe_extract_archive(\n                archive_path,\n                temp_path,\n                error_type=PresetValidationError,\n            )\n\n            pack_dir = temp_path\n            manifest_path = pack_dir / \"preset.yml\"\n\n            if not manifest_path.exists():\n                subdirs = [d for d in temp_path.iterdir() if d.is_dir()]\n                if len(subdirs) == 1:\n                    pack_dir = subdirs[0]\n                    manifest_path = pack_dir / \"preset.yml\"\n\n            if not manifest_path.exists():\n                raise PresetValidationError(\n                    \"No preset.yml found in archive\"\n                )\n\n            return self.install_from_directory(pack_dir, speckit_version, priority, force=force)\n\n    def install_from_zip(\n        self,\n        zip_path: Path,\n        speckit_version: str,\n        priority: int = 10,\n        force: bool = False,\n    ) -> PresetManifest:\n        \"\"\"Backward-compatible wrapper for archive installation.\"\"\"\n        return self.install_from_archive(\n            zip_path,\n            speckit_version,\n            priority,\n            force=force,","sourceCodeStart":3743,"sourceCodeEnd":3779,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L3743-L3779","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before: archive layout\nmy-preset/\nREADME.md          # second top-level entry breaks single-subdir detection\nmy-preset/preset.yml\n\n# after\npreset.yml         # at root of archive\nscripts/\ncommands/","handlingStrategy":"validation","validationCode":"import tarfile, zipfile\n\ndef archive_layout_ok(path) -> bool:\n    names = []\n    if zipfile.is_zipfile(path):\n        names = [n for n in zipfile.ZipFile(path).namelist() if not n.startswith(\"__MACOSX\")]\n    else:\n        names = tarfile.open(path).getnames()\n    top = {n.split(\"/\", 1)[0] for n in names if n.strip()}\n    root_ok = any(n.split(\"/\", 1)[0] == \"preset.yml\" or n == \"preset.yml\" for n in names)\n    single_sub = len(top) == 1 and any(n.count(\"/\") == 1 and n.endswith(\"/preset.yml\") for n in names)\n    return root_ok or single_sub","typeGuard":null,"tryCatchPattern":"try:\n    manager.install_from_archive(arch, ver)\nexcept PresetValidationError as e:\n    if \"No preset.yml\" in str(e):\n        # repack with preset.yml at root (or single top-level dir) and retry\n        ...","preventionTips":["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."],"tags":["preset","install","archive","packaging"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}