Repository navigation
fix(integrations): move extension artifacts on integration uninstall - #4911
Merged
Merged
Conversation
Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
`specify integration uninstall` tore down the integration files but left the extension and preset commands and skills registered for that agent, and when it promoted another integration to default it did not register the enabled extensions and presets for it, unlike `use` and `switch`. github#4862 added both steps for catalog-installed adapters in the `external_lifecycle` wrapper only. Do them in the uninstall command for every integration, including the no-manifest path, and drop the wrapper copy so external adapters do not run them twice. Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Package reconciliation can orphan stale-adapter contributions, and built-in preset behavior lacks required regression coverage.
2 open findings
What changed in this PR
Moves extension and preset artifact migration into the integration uninstall command for built-in and external integrations.
Changes:
- Cleans artifacts for uninstalled integrations.
- Registers artifacts for a promoted default integration.
- Adds extension regressions and updates uninstall documentation.
| File | Description |
|---|---|
command_uninstall.py |
Handles artifact cleanup and fallback registration. |
_lifecycle.py |
Removes duplicate external-adapter handling. |
test_command_uninstall.py |
Adds extension uninstall regressions. |
integrations.md |
Documents uninstall behavior. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Collaborator
|
Please address Copilot feedback |
The uninstall reconciliation loop also removes packages whose records are no longer in the integration state. The previous commit dropped the extension and preset cleanup for those packages, so their files and registry entries stayed behind once the adapter was gone. Restore it for every removed record other than the uninstalled key, which the command already cleans up. Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
Contributor
Author
Collaborator
|
Thank you! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
specify integration uninstall <agent>removes the integration's own files but leaves the extension and preset commands and skills registered for that agent. When the uninstalled integration was the default, it also promotes another integration without registering the enabled extensions and presets for it.useandswitchboth do these steps;uninstalldid not. Reproduction with the bundledgitextension:Presets go the same way: with the bundled
leanpreset installed, uninstalling claude leaves five of its override skills in.claude/skills/, and gemini keeps the core commands instead of the preset's.#4862 added these two steps for catalog-installed adapters only, inside the
external_lifecyclewrapper. That wrapper returns before its transaction when no external adapter is involved, so built-in integrations never reached them. This PR does them in the uninstall command itself, for every integration and also on the "no manifest" path, using the same helpers (the fallback messages are the wrapper's; the cleanup messages followswitch). The wrapper now cleans up only the stale adapter records it removes alongside the uninstalled integration, and no longer registers the fallback, so external adapters do not run the steps twice. The integrations reference now describes the behaviour under Uninstall.Behaviour notes: the extensions and presets stay installed; only the files registered for the removed agent go. As with
switch, an extension command file the user edited is removed with its agent.Testing
test_uninstall_unregisters_extension_and_preset_artifacts, with the bundledgitextension and a dev preset that overridesspeckit.specify: the extension skill, the preset skill and their registry entries are gone after uninstall. Onmain:Extension skill should be removed with its agent.test_uninstall_default_registers_contributions_for_fallback, run with and without the integration manifest: the promoted default gets the extension skill and the preset override. Onmain:Promoted default should receive enabled extensions(both cases).test_uninstall_non_default_preserves_default_contributions: uninstalling a non-default integration leaves the default's extension and preset files and registry entries in place.Preset skill should be removed with its agent,Promoted default should receive enabled presets), so the preset steps are covered on their own.test_uninstall_cleans_up_contributions_of_stale_adapter_recordintest_installed_adapters.py: an external adapter whose record is no longer inintegration.jsonis removed when another integration is uninstalled, and its extension and preset files and registry entries go with it. It passes onmainand fails on this PR's first revision (535a397), which left.sample-agent/skills/speckit-git-commit/SKILL.mdbehind.test_command_uninstall.pywithtest_installed_adapters.py: 683 passed.tests/specify_cli,tests/integrationsandtests/test_extensions.py: 7782 passed, 7 skipped.leanpreset, against this branch:.claude/holds no files afterwards,.gemini/commands/speckit.git.*.tomlexist, and gemini's commands come fromlean.Equivalent local checks: tests ran with
.venv/bin/python -m pytestand the CLI with the venv'sspecify, on macOS.AI Disclosure
AI disclosure: This contribution was produced by Claude Code running autonomously on behalf of @Chaoqi31. An audit agent (Claude Fable 5.1, max reasoning effort) found the bug and wrote a failing reproduction. A fix agent (Claude Opus 5, xhigh effort) wrote the fix, tests and docs sentence. A separate review agent (Claude Fable 5.1, max reasoning effort) checked the branch, and the orchestrating agent (Claude Opus 5) reran the CLI reproductions and wrote the commits and this description. Claude Code (Claude Opus 5) also wrote the follow-up commits for review feedback. AI generated the code, tests and text. No human reviewed the diff line by line before submission.