Skip to content

[Bug]: extension add overwrites a pre-existing user skill; extension remove then deletes it #5380

Open

Description

@markuswondrak

Bug Description

If the project already contains a user-authored skill directory with the same name as an extension skill (e.g. .claude/skills/speckit-sample-review/SKILL.md), native registration overwrites the user's SKILL.md without warning or backup. A later extension remove deletes it, so the user's content is lost. Other files in the directory survive.

With extension skill resources (#5366), a failed extension update that introduces such a command can additionally leave files written by the attempt inside the user's directory, because the update rollback only tracks skill directories that were absent beforehand.

Steps to Reproduce

import yaml
from specify_cli import save_init_options
from specify_cli.extensions import ExtensionManager

def test_repro(tmp_path):
    project = tmp_path / "p"; (project / ".specify").mkdir(parents=True)
    user = project / ".claude/skills/speckit-sample-review"; user.mkdir(parents=True)
    (user / "SKILL.md").write_text("user skill"); (user / "notes.txt").write_text("user")
    save_init_options(project, {"ai": "claude", "ai_skills": True, "script": "sh"})
    ext = tmp_path / "ext"; (ext / "commands").mkdir(parents=True)
    (ext / "commands/review.md").write_text("---\ndescription: d\n---\nhello\n")
    (ext / "extension.yml").write_text(yaml.safe_dump({"schema_version": "1.0",
        "extension": {"id": "sample", "name": "S", "version": "1.0.0", "description": "d", "author": "t"},
        "requires": {"speckit_version": ">=0.1.0"},
        "provides": {"commands": [{"name": "speckit.sample.review", "file": "commands/review.md"}]}}))
    ExtensionManager(project).install_from_directory(ext, "1.0.0")
    assert (user / "SKILL.md").read_text() != "user skill"   # overwritten
    ExtensionManager(project).remove("sample")
    assert not (user / "SKILL.md").exists()                  # user content lost
    assert (user / "notes.txt").exists()

Expected Behavior

An existing, unowned user skill is not overwritten silently (skip with a warning, or require --force with a backup), and removal never deletes content the extension did not create. The supplemental skills route already skips pre-existing unowned skill directories.

Actual Behavior

The user SKILL.md is overwritten on install and deleted on remove.

Specify CLI Version

main @ 7ec720e (1.3.4.dev0)

AI Agent

Claude Code (native SKILL.md integrations)

Operating System

Linux 6.17

Python Version

3.14.4

Error Logs

n/a

Additional Context

Found during review of #5366 (extension skill resources). The resource-specific leftover on a failed update is deferred here because it is only triggered by this pre-existing collision.

AI Disclosure

Drafted with the pi coding agent (orchestrator model: Claude Opus 5.5 via GitHub Copilot; implementation/review subagents: GPT-6.1-terra, GPT-6-astra), human-supervised by the filer. Reproductions were run locally by the agent and reviewed by the filer before filing.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage-must-haveVerdict: high-value, important work for Spec Kit — do first

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions