{"record":{"id":"1a8b48381d2297fb","repo":"headroomlabs-ai/headroom","slug":"expected-exactly-one-pattern-in-out-dir-found","errorCode":null,"errorMessage":"expected exactly one {pattern} in {out_dir}, found {len(matches)}","messagePattern":"expected exactly one (.+?) in (.+?), found (.+?)","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"scripts/build_python_release_smoke.py","lineNumber":90,"sourceCode":"        \"build\",\n        \"--out\",\n        out_dir,\n        \"--interpreter\",\n        python_exe,\n    ]\n    if release:\n        build_args.append(\"--release\")\n    else:\n        build_args.extend([\"--profile\", profile])\n    run(build_args, env=env)\n\n    run([python_exe, \"-m\", \"maturin\", \"sdist\", \"--out\", out_dir], env=env)\n\n\ndef find_one_artifact(out_dir: Path, pattern: str) -> Path:\n    matches = sorted(out_dir.glob(pattern))\n    if len(matches) != 1:\n        raise SystemExit(f\"expected exactly one {pattern} in {out_dir}, found {len(matches)}\")\n    return matches[0]\n\n\ndef verify_wheel(wheel: Path, expected_version: str) -> None:\n    with zipfile.ZipFile(wheel) as archive:\n        names = set(archive.namelist())\n        native_members = [\n            name\n            for name in names\n            if name.startswith(\"headroom/_core\") and Path(name).suffix.lower() in {\".pyd\", \".so\"}\n        ]\n        if not native_members:\n            raise SystemExit(f\"{wheel.name} does not contain headroom/_core native extension\")\n\n        metadata_members = [name for name in names if name.endswith(\".dist-info/METADATA\")]\n        if len(metadata_members) != 1:\n            raise SystemExit(\n                f\"{wheel.name} should contain exactly one dist-info/METADATA, \"","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/scripts/build_python_release_smoke.py#L72-L108","documentation":"find_one_artifact() globs the output directory for a wheel or sdist pattern and requires exactly one match. Zero matches means the build produced nothing with the expected name; two or more means the pattern is ambiguous (typically several wheels from one maturin run, or leftovers from repeated builds).","triggerScenarios":"Multiple wheels for different platforms/tags in out_dir; running maturin twice into the same directory; a version mismatch so the glob pattern (which includes the version) matches nothing; sdist and wheel both matching a too-loose pattern.","commonSituations":"Rebuilding after a version bump without clearing out_dir; maturin configured with multiple interpreters (`-i python3.11 -i python3.12`) producing one wheel per interpreter; abi3 wheel naming differing from the expected pattern.","solutions":["Inspect out_dir (`ls`) and reconcile what is there against the pattern passed to find_one_artifact.","If duplicates exist, clear the directory and rerun so exactly one artifact per pattern is produced.","If zero match, verify the version used in the pattern equals the version in the built artifact filenames (pyproject version vs expected_version).","If maturin is invoked with multiple interpreters, restrict to one or tighten the pattern per tag."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef expect_single_match(out_dir: Path, pattern: str) -> Path | None:\n    matches = sorted(out_dir.glob(pattern))\n    return matches[0] if len(matches) == 1 else None\n# before the smoke step: assert expect_single_match(...) is not None,\n# and if it is None, print sorted(p.name for p in out_dir.iterdir())","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build into a fresh directory per run so exactly one artifact per pattern can exist.","Pin one interpreter target for maturin per smoke run to avoid multiple wheels.","Keep the version used in glob patterns sourced from pyproject.toml, not a separate constant."],"tags":["python","maturin","wheel","build"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}