Skip to content

[Bug]: Extension skill references are rewritten to project-root-relative paths and miss nested directories #5363

Closed

Description

@markuswondrak

Bug Description

When an extension command is installed as an agent skill, rewrite_extension_paths (src/specify_cli/agents.py, introduced in #2101) rewrites references so they point to .specify/extensions/<id>/…. This has two problems:

  1. Root-relative output. The result is always .specify/extensions/<id>/…, relative to the project root. In agents that resolve skill-relative paths from the directory of SKILL.md (for example .github/skills/<name>/SKILL.md), the path resolves to .github/skills/<name>/.specify/… and the file is not found. ../../../.specify/extensions/<id>/… works.
  2. Only top-level directories are matched. Only directories directly in the extension root are rewritten. A reference such as references/template.md is left unchanged when the file lives in skills/<name>/references/, because no top-level references/ directory exists.

Only SKILL.md is written to the skill directory. All other extension files stay in .specify/extensions/<id>/.

Steps to Reproduce

  1. Create an extension with skills/example/references/template.md and a command that says Read `references/template.md` .
  2. Install it in a project with a skills-based agent where skills are in .github/skills/.
  3. Inspect .github/skills/<ext>-<cmd>/SKILL.md. The reference is unchanged.
  4. Change the command to Read `skills/example/references/template.md` and reinstall. The reference becomes .specify/extensions/example/skills/example/references/template.md, which the agent cannot resolve from the SKILL.md directory.

Expected Behavior

References resolve from the skill, either via a path relative to the skill directory (for example ../../../.specify/extensions/<id>/…) or because referenced files are available next to SKILL.md. References into nested extension directories are handled too.

Actual Behavior

Nested references are not rewritten, and rewritten references are root-relative and not found when the agent resolves from the SKILL.md directory.

Specify CLI Version

0.8.2.dev0 (main checkout)

AI Agent

GitHub Copilot

Operating System

Not relevant (platform-independent path handling)

Python Version

Not relevant (platform-independent path handling)

Error Logs

N/A

Additional Context

Workaround for extension authors: use paths from the extension root (skills/example/references/template.md), preceded by whitespace, backtick, quote or (, and tell the agent in AGENTS.md to resolve .specify/… references from the project root.

Behavior was confirmed in the code and with the rewrite function in isolation, not with a full specify extension add. Resolution behavior in other agents is not verified.

Alternatives to consider

  1. Relative path from the skill directory. Generate the reference with os.path.relpath from the skill directory to .specify/extensions/<id>/…, for example ../../../.specify/extensions/<id>/… for .github/skills/<name>/. This keeps a single copy of the files, but the path depends on the agent's skills directory and its depth.
  2. Symlinks in the agent-native skill directory. Create symlinks next to SKILL.md that point to the referenced files or directories in .specify/extensions/<id>/. References like references/template.md then work as written, with no rewrite. Caveats: symlinks are unreliable on Windows without developer mode or admin rights, and some agents or tools may not follow them. The dev-mode install already uses symlinks for SKILL.md, so there is precedent.
  3. Copy the files into the skill directory. Copy the referenced files next to SKILL.md. This matches the usual Agent Skills layout and works everywhere, with no path rewriting. The cost is duplicated files that can drift from .specify/extensions/<id>/. Update, removal and rollback (_command_update_transaction.py, _find_extension_skill_dirs) would also need to track and clean up these files.

AI Disclosure

Drafted with GitHub Copilot CLI (Claude Sonnet 5.5). The code analysis was AI-assisted and reviewed by the filer.

Activity

  1. github-actions commented on Nov 10, 2026

    @github-actions
    Contributor

    Bug assessment — extension-skill-path-rewrite: Valid · severity medium


    Bug Assessment: Extension skill path rewriting breaks nested refs and skill-relative resolution

    Report (summarized)

    Issue #5363 (by @markuswondrak) reports that extension command references are rewritten to .specify/extensions/<id>/..., but this can fail for skills installed under .github/skills/.../SKILL.md when the agent resolves paths relative to SKILL.md rather than repository root. The report also states that nested references (for example references/template.md when the file is under skills/<name>/references/) are not rewritten.

    Symptom

    Extension command content in generated SKILL.md can contain rewritten paths that do not resolve from the skill directory, and some nested file references are left unchanged. Expected behavior is that referenced files are resolvable from the installed skill context, including nested extension directories.

    Reproduction

    1. Create an extension with nested assets (for example skills/example/references/template.md) and a command body that references references/template.md.
    2. Install for a skills-based agent that writes to a skill directory (for example Copilot skills under .github/skills/.../SKILL.md).
    3. Inspect generated SKILL.md and observe whether nested reference text is rewritten and whether rewritten .specify/extensions/... path resolves from the skill file location.

    [NEEDS CLARIFICATION: full end-to-end runtime confirmation of path resolution behavior was not provided for every agent, but code evidence and isolated function behavior support the report.]

    Suspected Code Paths

    • src/specify_cli/agents.py:237 (rewrite_extension_paths) — primary rewrite routine used during extension command/skill rendering.
    • src/specify_cli/agents.py:258 — collects only top-level subdirectories from extension_dir.iterdir(), which excludes nested directory names not present at root.
    • src/specify_cli/agents.py:274-275 — replacement is hard-coded to .specify/extensions/{extension_id}/{subdir}/ via regex substitution, producing repository-root-style paths.
    • src/specify_cli/extensions/__init__.py:1676 — skills registration path invokes registrar.rewrite_extension_paths(...) before writing SKILL.md.
    • tests/test_extension_skills.py:1099-1156 — existing tests assert .specify/extensions/... rewriting for top-level subdirs but do not cover nested-reference rewriting or skill-directory-relative resolution.
    • src/specify_cli/integrations/copilot/__init__.py:123,126 — Copilot skills are written under .github/skills with /SKILL.md, matching the reported context.

    Root Cause Hypothesis

    rewrite_extension_paths is intentionally conservative but currently keyed to top-level folder names only, so it cannot detect/reference nested paths that do not start with a root-level folder name present in the extension root. It also emits a fixed .specify/extensions/... prefix without considering the final skill file directory depth. That combination explains both reported symptoms. Confidence: high.

    Proposed Remediation

    Preferred: make rewrite context-aware and path-accurate in two parts:

    1. Replace the top-level-subdir heuristic with path-token detection that resolves candidate relative references against the extension root (with existing traversal/safety guards), then rewrites any existing target into its installed extension path.
    2. For skills output (/SKILL.md targets), compute reference strings relative to the generated skill directory (for example via os.path.relpath from <agent skills dir>/<skill>/ to .specify/extensions/<id>/...), while preserving existing behavior for non-skill command layouts if needed.

    This keeps single-source extension assets under .specify/extensions/ while producing references that remain resolvable in skills directories at varying depths.

    Alternatives (optional):

    • Copy referenced files beside SKILL.md during install (portable, but introduces duplication/drift and lifecycle cleanup complexity).
    • Symlink referenced files into skill directories (lower duplication but weaker Windows/tooling portability and symlink policy risk).

    Files likely to change:

    • src/specify_cli/agents.py
    • src/specify_cli/extensions/__init__.py
    • tests/test_extension_skills.py

    Tests to add or update:

    • Add a regression test where a nested reference (e.g. references/template.md backed by skills/example/references/template.md) is rewritten correctly.
    • Add a Copilot-skills-mode test asserting installed references are resolvable from .github/skills/<skill>/SKILL.md (relative path expectation).
    • Keep/adjust existing top-level rewrite tests to ensure prior supported patterns still pass.

    Risks & Considerations

    • Behavior differences across agent integrations (some may resolve from repo root, others from skill file location) require clear normalization rules per output mode.
    • Rewrite broadening can create false positives if regex boundaries are too permissive; preserve existing boundary guards and add negative tests.
    • Update/remove flows must remain consistent with any new reference-shape assumptions.

    Open Questions

    • [NEEDS CLARIFICATION: should path rewriting semantics be unified across all skill-based integrations, or vary by integration resolver behavior?]
    • [NEEDS CLARIFICATION: should extension authors be required to reference files from extension root, or should nested shorthand references be officially supported and normalized?]

    Generated by 🐛 Assess Bug from Labeled Issue for #5363 · copilot · gpt53codex · 23.1 AIC · ⌖ 0.602 AIC · ⊞ 24.2K · ◷

  2. added
    bug-fixTrigger the bug-fix agentic workflow
    triage-must-haveVerdict: high-value, important work for Spec Kit — do first
    on Nov 10, 2026
  3. github-actions commented on Nov 10, 2026

    @github-actions
    Contributor

    Pull request created: #5364

    Generated by Fix Bug from Labeled Issue · copilot · gpt53codex · 53.4 AIC · ⌖ 0.745 AIC · ⊞ 26.5K

  4. github-actions commented on Nov 10, 2026

    @github-actions
    Contributor

    Implemented extension-skill-path-rewrite and opened a draft fix PR from fix/5363-extension-skill-path-rewrite for maintainer review: main...fix/5363-extension-skill-path-rewrite

    Summary: extension skill path rewriting now rewrites only to existing extension assets, supports nested shorthand references via unique suffix resolution, and emits skill-directory-relative .specify/extensions/... paths for generated SKILL.md files.

    Next stage: after review of the draft PR, validate the fix with the stage-3 bug-test workflow if this repository has it configured.

    fix-proposed exists in this repository and is being applied for tracking.

    Generated by 🛠️ Fix Bug from Labeled Issue for #5363 · copilot · gpt53codex · 53.4 AIC · ⌖ 0.745 AIC · ⊞ 26.5K · ◷

  5. github-actions commented on Nov 10, 2026

    @github-actions
    Contributor

    Bug test — extension-skill-path-rewrite: ❌ failing · 0 passed, 3 failed, 0 skipped · fix from linked PR #5364


    Bug Test Report: Extension skill path rewrite regressions

    Summary

    Tested the proposed fix from PR #5364 against the assessment-identified regression tests. All three relevant tests failed, indicating the fix does not currently satisfy the expected nested and skill-relative path rewrite behavior.

    Test Results

    Metric Count
    Passed 0
    Failed 3
    Skipped 0
    Errored 0
    Duration ~0.15s

    Failures (if any)

    • tests/test_extension_skills.py::TestExtensionSkillRegistration::test_skill_registration_rewrites_extension_subdir_paths — expected ../../../.specify/extensions/path-ext/knowledge-base/agent-scores.yaml not present in generated SKILL.md content.
    • tests/test_extension_skills.py::TestExtensionSkillRegistration::test_skill_registration_rewrites_nested_extension_refs — expected nested rewrite to ../../../.specify/extensions/nested-ref-ext/skills/example/references/template.md; content remained Read references/template.md..
    • tests/test_extension_skills.py::TestExtensionSkillRegistration::test_copilot_skill_registration_rewrites_relative_to_skill_dir — expected ../../../.specify/extensions/copilot-path-ext/skills/copilot/references/guide.md not present; content remained Read skills/copilot/references/guide.md..

    Verification vs. Historical Fix

    not applicable (no historical fix referenced)

    Notes & Caveats

    Generated by 🧪 Test a Bug Fix from a Labeled Issue for #5363 · copilot · gpt53codex · 27.1 AIC · ⌖ 0.641 AIC · ⊞ 26.8K · ◷

  6. markuswondrak commented on Nov 10, 2026

    @markuswondrak
    ContributorAuthor

    @mnriem Could you share your view on the copy-resources-into-the-skill-directory alternative before we extend the path-rewriting approach further in #5364?

    My preference would be to make this an explicit opt-in, through a setting in extension.yml or a dedicated CLI flag. Existing installations would retain the current path-rewriting behavior unless copying is explicitly enabled. When enabled, each generated skill would be installed together with its required bundled resources, for example:

    .github/skills/speckit-example-review/
    ├── SKILL.md
    └── references/
        └── reviewer.md
    

    The skill could then simply reference references/reviewer.md, rather than reaching back into .specify/extensions/<id>/ through generated ../../../ paths.

    The Agent Skills specification describes a skill directory containing SKILL.md plus optional scripts, references, and assets. Its file-reference guidance explicitly says:

    When referencing other files in your skill, use relative paths from the skill root.

    I read this as favoring self-contained packaging of bundled skill resources, although it does not prohibit external dependencies. Copying those resources seems closer to that model than generating only SKILL.md and leaving its supporting files elsewhere.

    The extension under .specify/extensions/<id>/ could remain the source of truth; skill-local copies would be generated installation outputs. Updates, removal, and rollback would need to track them and preserve user modifications appropriately. That is additional lifecycle work, but perhaps preferable to increasingly complex path detection and rewriting.

    Within this opt-in mode, I would favor regular copies rather than relying on symlinks, for portability. Your comment on #2988 documents the Codex limitation for symlinked SKILL.md files, though that does not necessarily apply to resource links.

    This would not mean blindly copying the entire extension: shared configuration and scripts requiring the project root as their working directory would still need explicit handling.

    Would you support an opt-in mechanism for skill-local resource copies, and would you prefer the opt-in to live in extension.yml, in the CLI, or both? Is there an architectural reason to avoid this approach? If it is too broad for this bugfix, should we agree on the intended packaging model in a separate issue first?


    AI assistance: Researched and drafted for @markuswondrak using pi with GitHub Copilot / gpt-6-astra, in interactive, human-directed mode. No implementation or runtime testing performed.

  7. mnriem commented on Nov 10, 2026

    @mnriem
    Collaborator

    We would need to support both. The alongside as the new normal and the in the extension as the deprecated but still supported route

  8. mnriem commented on Nov 10, 2026

    @mnriem
    Collaborator

    It would be part of the extension.yml I would think

  9. markuswondrak commented on Nov 10, 2026

    @markuswondrak
    ContributorAuthor

    Got it. Will create a new issue for that.

    I would then close this one or do you like this one to be implemented?

    We actually don't need it if we get the "copy"-version.

  10. mnriem commented on Nov 10, 2026

    @mnriem
    Collaborator

    Less is more so yea lets close it then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions