github/spec-kit · error · ValueError

manifest.project_root ({manifest.project_root}) does not mat

Error message

manifest.project_root ({manifest.project_root}) does not match project_root ({project_root_resolved})

What it means

Raised by ForgeIntegration.setup() (src/specify_cli/integrations/forge/__init__.py:124) when the IntegrationManifest's recorded project_root differs from the resolved project_root argument. All custom setup() implementations in spec-kit verify manifest-to-root binding before writing, so a manifest created for one location (or an unresolved/symlinked variant of it) is rejected here.

Source

Thrown at src/specify_cli/integrations/forge/__init__.py:124

    def setup(
        self,
        project_root: Path,
        manifest: IntegrationManifest,
        parsed_options: dict[str, Any] | None = None,
        **opts: Any,
    ) -> list[Path]:
        """Install Forge commands with custom processing.

        Extends MarkdownIntegration.setup() to inject Forge-specific transformations
        after standard template processing.
        """
        templates = self.list_command_templates()
        if not templates:
            return []

        project_root_resolved = project_root.resolve()
        if manifest.project_root != project_root_resolved:
            raise ValueError(
                f"manifest.project_root ({manifest.project_root}) does not match "
                f"project_root ({project_root_resolved})"
            )

        dest = self.commands_dest(project_root).resolve()
        try:
            dest.relative_to(project_root_resolved)
        except ValueError as exc:
            raise ValueError(
                f"Integration destination {dest} escapes "
                f"project root {project_root_resolved}"
            ) from exc
        dest.mkdir(parents=True, exist_ok=True)

        script_type = opts.get("script_type", "sh")
        arg_placeholder = self.registrar_config.get("args", "{{parameters}}")
        created: list[Path] = []

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Create the manifest with the identical resolved root: manifest = IntegrationManifest(project_root=project_root.resolve()).
  2. Pass the same Path object (or an identically resolved one) to setup().
  3. Never share a manifest instance between different project roots.
  4. Prefer the specify CLI (specify init --integration forge) which constructs the manifest correctly.

Example fix

# before
manifest = IntegrationManifest(project_root=Path.cwd())
ForgeIntegration().setup(Path("/home/u/proj"), manifest)

# after
root = Path("/home/u/proj").resolve()
manifest = IntegrationManifest(project_root=root)
ForgeIntegration().setup(root, manifest)
Defensive patterns

Strategy: validation

Validate before calling

root = project_root.resolve()
if manifest.project_root != root:
    manifest = IntegrationManifest(project_root=root)

Try / catch

try:
    integration.setup(root, manifest)
except ValueError as e:
    if "manifest.project_root" in str(e):
        manifest = IntegrationManifest(project_root=root.resolve())
        integration.setup(root, manifest)
    else:
        raise

Prevention

When it happens

Trigger: Calling ForgeIntegration().setup(project_root, manifest) where manifest was instantiated with a different or non-equivalently-resolved root; e.g. manifest built from '.' while setup receives an absolute path, or the manifest object belongs to another project entirely.

Common situations: Test harnesses that create manifests in tmp_path but call setup with tmp_path_resolved variants; reusing one manifest across nested sub-projects; symlinked working directories (/tmp vs /private/tmp) making resolve() produce different strings.

Related errors


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