{"record":{"id":"774cf2f1f76c9358","repo":"github/spec-kit","slug":"refusing-to-scaffold-outside-the-repository-root","errorCode":null,"errorMessage":"Refusing to scaffold outside the repository root: {target}","messagePattern":"Refusing to scaffold outside the repository root: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integration_scaffold.py","lineNumber":183,"sourceCode":"                project_root / \"src\" / \"specify_cli\" / \"integrations\" / \"__init__.py\"\n            ).is_file(),\n            (project_root / \"tests\" / \"integrations\").is_dir(),\n        )\n    )\n\n\ndef _assert_safe_scaffold_target(project_root: Path, target: Path) -> None:\n    \"\"\"Refuse to scaffold through a symlinked path that could escape the repo.\n\n    Walks each component of *target* under *project_root* and rejects any\n    existing symlinked directory (or symlinked target), then confirms the\n    write destination still resolves inside the repository root. Mirrors the\n    symlink-aware guarding used for integration manifests.\n    \"\"\"\n    try:\n        rel = target.relative_to(project_root)\n    except ValueError:\n        raise ValueError(\n            f\"Refusing to scaffold outside the repository root: {target}\"\n        ) from None\n\n    current = project_root\n    for part in rel.parts:\n        current = current / part\n        if current.is_symlink():\n            label = current.relative_to(project_root).as_posix()\n            raise ValueError(f\"Refusing to scaffold through symlinked path: {label}\")\n\n    root_resolved = project_root.resolve()\n    try:\n        target.parent.resolve().relative_to(root_resolved)\n    except (OSError, ValueError):\n        raise ValueError(\n            f\"Refusing to scaffold outside the repository root: {target}\"\n        ) from None\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integration_scaffold.py#L165-L201","documentation":"Raised by _assert_safe_scaffold_target in integration_scaffold.py when the scaffold target file cannot be expressed as a relative path under project_root (Path.relative_to raises ValueError). This is the first of two 'outside the repository root' guards: it catches targets that are not lexically under the supplied root before any symlink resolution happens. The scaffold command refuses to write integration/test skeletons anywhere but inside the Spec Kit source tree.","triggerScenarios":"Calling scaffold_integration(project_root, key, type) with a target like project_root/'src'/'specify_cli'/'integrations'/pkg/'__init__.py' when project_root is not an ancestor of target: e.g. project_root was resolved to a different path (symlinked checkout), target was built from an absolute path from another tree, or the caller passed the wrong root.","commonSituations":"Running the scaffold CLI from a subdirectory so cwd-derived project_root disagrees with the target layout; a symlinked repo checkout where the naive root and the real root differ; CI checking out to a temp path while reusing cached absolute paths.","solutions":["Run the scaffold command from the actual Spec Kit repository root so project_root is the parent of src/specify_cli and tests/integrations.","Pass project_root as the repo root (the directory containing src/specify_cli) rather than a subdirectory or a symlinked alias.","If the checkout is symlinked, cd into the real (resolved) path or resolve project_root with Path.resolve() before calling scaffold_integration."],"exampleFix":"# before\nscaffold_integration(Path(\"~/repos/spec-kit-link\").expanduser(), \"my-agent\", \"markdown\")\n# after (use the real repo root)\nroot = Path(\"~/repos/spec-kit\").expanduser().resolve()\nscaffold_integration(root, \"my-agent\", \"markdown\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef safe_scaffold_target(project_root: Path, target: Path) -> bool:\n    try:\n        target.relative_to(project_root)\n    except ValueError:\n        return False\n    return True\n\n# before calling scaffold_integration:\nroot = root.resolve()\nassert safe_scaffold_target(root, root / \"src\" / \"specify_cli\" / \"integrations\" / pkg)","typeGuard":null,"tryCatchPattern":"try:\n    scaffold_integration(root, key, itype)\nexcept ValueError as exc:\n    if \"outside the repository root\" in str(exc):\n        root = Path.cwd().resolve()  # re-anchor and retry once\n    else:\n        raise","preventionTips":["Always derive project_root with Path.resolve() before scaffolding.","Run scaffold commands from the repository root rather than passing constructed absolute paths.","In wrappers, assert target.relative_to(root) succeeds before invoking the API."],"tags":["filesystem","validation","scaffold","path-safety"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}