Skip to content

fix(extensions): do not overwrite pre-existing user skills on install - #5386

Open
sx4im wants to merge 1 commit into
github:mainfrom
sx4im:fix/5380-extension-overwrites-user-skill
Open

sx4im wants to merge 1 commit into
github:mainfrom
sx4im:fix/5380-extension-overwrites-user-skill

Conversation

@sx4im

@sx4im sx4im commented Nov 11, 2026

Copy link
Copy Markdown

What broke

extension add overwrote a user's own pre-existing skill when an extension command landed on the same path, and extension remove then deleted the user's skill. The supplemental route already skipped such files, but native command registration ran first and overwrote them, stamping the extension's marker on the file.

What changed

CommandRegistrar.register_commands now checks whether an existing SKILL.md was created by the extension being installed (native, supplemental, or preset marker, or a dev-cache symlink) and skips it with a warning when it was not. A skipped skill is never registered, so remove never targets it.

How verified

New regression test in tests/test_extension_skills.py: install with a pre-existing user skill leaves the skill and its notes file untouched, and remove leaves them in place. The test fails on main and passes with the fix. Targeted suites pass: extension skills, registration, extensions, command-add, presets, bundles (1699 passed, 1 skipped). ruff check clean.

Fixes #5380

Native command registration wrote extension skills over any existing
SKILL.md at the same path, even when the file was a user's own skill
with no extension marker. The supplemental route already skipped such
files, but it ran after native registration had already overwritten
them. The overwritten file then carried the extension's marker, so
extension remove deleted the user's skill as well.

Registering a /SKILL.md output now checks whether the existing file
was created by this extension (native, supplemental, or preset marker,
or a dev-cache symlink) and skips it with a warning when it was not.
A skipped skill is never added to the registry, so remove never
targets it either.

Adds a regression test: install with a pre-existing user skill leaves
the skill and its notes file untouched, and remove leaves them in
place.

Fixes github#5380
@sx4im
sx4im requested a review from mnriem as a code owner November 11, 2026 09:42
Copilot AI auto review requested due to automatic review settings November 11, 2026 09:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The ownership guards can incorrectly accept another extension’s symlink and read non-regular targets, risking unintended skill replacement, blocked installation, or memory exhaustion.

6 open issues
What changed in this PR

Adds collision protection for user-authored skills to Spec Kit’s shared command registrar, targeting the install/remove data-loss issue in #5380.

Changes:

  • Checks ownership before replacing native SKILL.md command and alias files, warning when registration is skipped.
  • Adds an install/remove regression test verifying that an existing user skill and its notes survive.

Validation used isolated checks; project suites and the before/after regression comparison were not run.

AI-generated review by GitHub Copilot (model unknown; automated; requester identity not provided).

File Description
tests/​test_extension_skills.py Tests user-skill preservation and exclusion from registration across installation and removal.
src/​specify_cli/​agents.py Adds ownership detection and collision guards for native command and alias skills.

Review effort: Max (auto)

Comment thread src/specify_cli/agents.py
Comment on lines +638 to +639
if target is not None and ".specify-dev" in target.parts:
return True
Comment thread src/specify_cli/agents.py
Comment on lines +640 to +643
try:
raw = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
return False
Comment thread src/specify_cli/agents.py
Comment on lines +645 to +647
source = (
fm.get("metadata", {}).get("source", "") if isinstance(fm, dict) else ""
)
Comment thread src/specify_cli/agents.py
Comment on lines +929 to +932
if (
agent_config["extension"] == "/SKILL.md"
and (dest_file.exists() or dest_file.is_symlink())
and not self._skill_file_owned_by_source(dest_file, source_id)
Comment thread src/specify_cli/agents.py
Comment on lines +934 to +937
# Never overwrite a pre-existing user skill: the supplemental
# skills route already skips unowned directories, and native
# registration must do the same (#5380). The command is left
# unregistered so a later removal cannot delete it either.
Comment thread src/specify_cli/agents.py
Comment on lines +1032 to +1035
if (
agent_config["extension"] == "/SKILL.md"
and (alias_file.exists() or alias_file.is_symlink())
and not self._skill_file_owned_by_source(alias_file, source_id)

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants