Skip to content

[Bug]: Failed extension add leaves native SKILL.md outputs behind #5379

Open

Description

@markuswondrak

Bug Description

When specify extension add fails after command rendering (e.g. hook registration or the final registry write fails), the rollback removes the installed package and registry entry, but native skill outputs (<agent skills dir>/speckit-<ext>-<cmd>/SKILL.md) written by the attempt are left behind. They are not recorded anywhere, so later lifecycle operations do not own them.

The generic integration's install rollback (rollback_generic_registration) already removes its outputs; the native route has no equivalent.

Steps to Reproduce

Minimal reproduction (pytest, Claude in skills mode; simulates a hook-write failure):

import yaml, pytest
from specify_cli import save_init_options
from specify_cli.extensions import ExtensionManager, HookExecutor

def test_repro(tmp_path, monkeypatch):
    project = tmp_path / "p"; (project / ".specify").mkdir(parents=True)
    (project / ".claude/skills").mkdir(parents=True)
    save_init_options(project, {"ai": "claude", "ai_skills": True, "script": "sh"})
    ext = tmp_path / "ext"; (ext / "commands").mkdir(parents=True)
    (ext / "commands/run.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.run", "file": "commands/run.md"}]},
        "hooks": {"after_tasks": {"command": "speckit.sample.run", "optional": True}}}))
    monkeypatch.setattr(HookExecutor, "register_hooks", lambda *a: (_ for _ in ()).throw(OSError("x")))
    with pytest.raises(OSError):
        ExtensionManager(project).install_from_directory(ext, "1.0.0")
    assert not ExtensionManager(project).registry.is_installed("sample")
    assert (project / ".claude/skills/speckit-sample-run/SKILL.md").exists()  # leftover

Expected Behavior

A failed install leaves no outputs that the attempt created; pre-existing files are restored unchanged.

Actual Behavior

.claude/skills/speckit-sample-run/SKILL.md remains after the failed install while the extension is not installed.

Specify CLI Version

main @ 7ec720e (1.3.4.dev0)

AI Agent

Claude Code (any native SKILL.md integration)

Operating System

Linux 6.17

Python Version

3.14.4

Error Logs

n/a (simulated failure)

Additional Context

Found while implementing extension skill resources (#5366). That change removes copied resources written by a failed attempt, but intentionally leaves this pre-existing SKILL.md gap unchanged.

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.

Activity

  1. added
    triage-must-haveVerdict: high-value, important work for Spec Kit — do first
    on Nov 11, 2026
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