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 HermesIntegration.setup() (src/specify_cli/integrations/hermes/__init__.py:100) when the manifest's recorded project_root does not equal the resolved project_root argument. Hermes installs into a global skills directory but still creates a project-local marker, so it performs the same standard manifest/root binding check as other integrations before writing anything.

Source

Thrown at src/specify_cli/integrations/hermes/__init__.py:100

    ) -> list[Path]:
        """Install command templates as global Hermes skills.

        Writes each skill directly to
        ``~/.hermes/skills/speckit-<name>/SKILL.md`` where Hermes
        discovers them at runtime.  No project-local SKILL.md copies are
        created — the global directory is the single source of truth.
        A project-local marker (``.hermes/skills/`` empty) is created
        so extension commands (e.g. git) can detect Hermes as an active
        integration.
        """
        templates = self.list_command_templates()
        if not templates:
            return []

        # Safety check: verify manifest project_root matches (standard pattern)
        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})"
            )

        script_type = opts.get("script_type", "sh")
        arg_placeholder = (
            self.registrar_config.get("args", "$ARGUMENTS")
            if self.registrar_config
            else "$ARGUMENTS"
        )

        global_skills_dir = self._hermes_home_skills_dir()
        global_skills_dir.mkdir(parents=True, exist_ok=True)

        created: list[Path] = []

        for src_file in templates:
            raw = src_file.read_text(encoding="utf-8")

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Create the manifest with project_root.resolve() and pass the identical resolved root to setup().
  2. Create a fresh manifest per project; never reuse across roots.
  3. Prefer 'specify init --integration hermes', which wires the manifest correctly.

Example fix

# before
manifest = IntegrationManifest(project_root=Path("~/proj").expanduser())
HermesIntegration().setup(Path("/Users/u/proj"), manifest)

# after
root = Path("/Users/u/proj").resolve()
manifest = IntegrationManifest(project_root=root)
HermesIntegration().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: HermesIntegration().setup(project_root, manifest) where manifest.project_root differs from project_root.resolve() — different directory, another project's manifest, or an unresolved/symlinked spelling of the same directory.

Common situations: Reusing a manifest created before changing directories; /tmp vs /private/tmp symlink differences on macOS; automated harnesses mixing tmp_path and its resolved form.

Related errors


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