Repository navigation
Conversation
`specify integration switch generic` run without `--integration-options`,
or with options that omit or blank out `--commands-dir`, uninstalled the
current integration first and only then failed:
Uninstalling current integration: claude
Removed 10 file(s)
Installing integration: generic
Error: Failed to install integration 'generic' during switch:
--commands-dir is required for the generic integration
By then Phase 1 has also deleted .specify/integration.json and cleared
ai and integration from init-options.json. With no other integration
installed, the rollback has nothing to fall back to, so the project is
left with no integration at all.
switch already validates the target's options before Phase 1 so that
invalid options cannot leave a project half switched, but neither
option parsing nor is_skills_mode() looks at IntegrationOption.required.
The only check for generic's required --commands-dir is in
GenericIntegration.setup(), which switch calls in Phase 2, after the
teardown. Reject a missing or blank required option before Phase 1.
Two rollback tests used a bare `switch generic` to make Phase 2 fail.
They now pass a commands dir outside the project, which setup() still
rejects after the teardown, so they keep covering the rollback path.
Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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 switch genericchecksgeneric's required--commands-dirtoo late. Running it without--integration-options, with options that leave out--commands-dir(e.g.--skills), or with a blank--commands-dir=uninstalls the current integration before it fails. If that was the only integration, the project is left with none:init-options.jsonalso loses itsaiandintegrationkeys.Root cause:
integration_switch()already validates the target's options before Phase 1 so that invalid options can't leave a project half switched (command_switch.py, the block before# Phase 1).is_skills_mode(). Neither_parse_integration_options()noris_skills_mode()readsIntegrationOption.required.generic's required--commands-diris inGenericIntegration._resolve_commands_dir(), whichsetup()calls. Switch reachessetup()in Phase 2.init-options.jsonand removed.specify/integration.json, so the rollback has nothing to fall back to.The fix adds a small
_missing_required_options()check next to the existing pre-Phase-1 validation. It exits 1 before any file is touched when a required option is missing or blank:Switching back to an already-installed
genericwithout passing options still works, because_resolve_integration_options()returns its stored parsed options, which includecommands_dir.Testing
Tested locally with
uv run specify --helpRan existing tests with
uv sync && uv run pytestTested with a sample project (if applicable): the transcripts above, from a
claudeproject initialized with this checkoutNew
test_switch_rejects_missing_required_option_before_uninstall, parametrized for no options, options without--commands-dir, and a blank--commands-dir=. It asserts exit 1, that the claude skills are still on disk, thatintegration.jsonandinit-options.jsonare byte-identical, and the error message. All three cases fail onmain(AssertionError: switch removed the current integration) and pass with the change.New
test_switch_to_generic_with_required_optioncovers the positive path.Two existing rollback tests (
test_failed_switch_keeps_fallback_metadata_consistent,test_failed_switch_rescaffolds_fallback_extensions) used a bareswitch genericonly to make Phase 2 fail. They now pass--commands-dir ../outside, whichsetup()still rejects after the teardown, so they keep covering the rollback path. They pass both with and without the fix.tests/specify_cli/integrations/test_command_switch.py: 30 passed. Withtests/integrations/test_integration_generic.py: 271 passed.Full suite (macOS, Python 3.13): 12334 passed, 376 skipped. 11 failures in the
tests/test_*_python_parity.pyfiles occur identically on unmodifiedmainon this machine under full-suite load; those files pass when run on their own (257 passed, 112 skipped).uvx ruff@0.15.0 check src tests: cleanAI Disclosure
AI disclosure: Claude Code (model: Claude Opus 5.5, max reasoning effort, autonomous agent mode) opened this PR on behalf of @yxshee. A Claude Code subagent (same model, autonomous) found the bug by exercising the CLI, then wrote the fix, the tests and the reproductions above. The lead agent independently re-ran the new tests against
main(3 failed) and the branch (30 passed), reproduced both transcripts above, reviewed the diff, and wrote this description. No human reviewed the change line by line before it was opened.🤖 Generated with Claude Code