Skip to content

Extension scripts need an absolute project root for reliable path resolution #3308

Description

@rhuss

Problem

Extension commands that invoke helper scripts need a reliable absolute path to those scripts at runtime. The project-relative paths that rewrite_project_relative_paths() produces (e.g., .specify/extensions/spex/scripts/foo.sh) only work when the agent's working directory is the project root. In practice, CWD shifts during a session and the relative paths break.

The most common scenario where this happens is git worktrees. When an agent creates a worktree for isolated feature development, the working directory changes to the worktree root, which is a different filesystem location from the main repo. Subagent spawning can also reset CWD, as can explicit cd commands in multi-step workflows. Any of these cause a relative script path to resolve against the wrong directory.

What we're doing today

In our extension (cc-spex), we currently work around this by using the Claude Code plugin system. Our scripts live in the plugin cache directory (~/.claude/plugins/cache/spex-plugin/spex/5.8.0/scripts/), and a context hook injects the absolute plugin root path into every prompt. Commands reference scripts as <PLUGIN_ROOT>/scripts/foo.sh, which always resolves correctly regardless of CWD.

This works, but it's entirely Claude Code-specific. The plugin cache, the context hook, the <plugin-root> injection are all Claude Code infrastructure. None of it exists on other agents, so the approach doesn't generalize.

The git rev-parse workaround

The agent-agnostic fallback is:

REPO_ROOT=$(git rev-parse --show-toplevel)
"$REPO_ROOT/.specify/extensions/spex/scripts/foo.sh"

This works from any CWD within the repo, including worktrees (--show-toplevel returns the worktree root, not the main repo root). But it requires a git invocation every time a command references a script, and it assumes the project is a git repository.

Proposal

A __PROJECT_ROOT__ template variable, resolved at install time to the absolute project path, would solve this cleanly. It fits the existing pattern of __AGENT__ and __SPECKIT_COMMAND_*__ substitutions in the template pipeline, and it would give extension authors a reliable way to reference their own scripts from any working directory.

Commands would reference scripts as:

__PROJECT_ROOT__/.specify/extensions/spex/scripts/foo.sh

This stays materialized (the absolute path is baked into the installed command file), project-local, and agent-agnostic.

One tradeoff: the resolved path becomes stale if the project directory moves. But that's the same tradeoff __AGENT__ already makes (agent name is baked in at install time), and specify init --refresh would re-resolve it.

Related: #3303 (extension portability discussion, where the maintainer suggested using extension-local paths for script discovery).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions