Skip to content

fix(extensions): delete the catalog config when the last entry is removed - #4909

Merged
mnriem merged 4 commits into
github:mainfrom
Chaoqi31:fix/extension-catalog-remove-last
Oct 13, 2026
Merged

mnriem merged 4 commits into
github:mainfrom
Chaoqi31:fix/extension-catalog-remove-last

Conversation

@Chaoqi31

@Chaoqi31 Chaoqi31 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

specify extension catalog remove <name> on the last project catalog prints "No catalogs remain in config. Built-in defaults will be used." but writes .specify/extension-catalogs.yml as catalogs: []. The extension catalog loader rejects an existing config with no entries (on purpose, see test_empty_project_config_raises_error), so every later catalog-backed command fails until the user deletes the file by hand:

$ specify extension catalog add https://only.example.com/catalog.json --name only
$ specify extension catalog remove only
✓ Removed catalog 'only'
No catalogs remain in config. Built-in defaults will be used.
$ cat .specify/extension-catalogs.yml
catalogs: []
$ specify extension catalog list
Error: Catalog config .../.specify/extension-catalogs.yml exists but contains no 'catalogs' entries. Remove the file to use built-in defaults, or add valid catalog entries.

extension search, extension info, extension add <id> for any id that is not bundled, and extension update once an extension is installed fail the same way.

This PR deletes the file when the last entry is removed, which is what specify integration catalog remove already does (IntegrationCatalog.remove_catalog, pinned by test_catalog_remove_final_entry_restores_defaults). The next layer in the resolution order then applies. That is the user-level ~/.specify/extension-catalogs.yml when one exists, and the built-in default and community catalogs otherwise. The message now reads "Project catalog override removed; user configuration or built-in defaults will be used." When SPECKIT_CATALOG_URL is set, it says that variable still takes precedence instead, because it overrides every catalog config. If deleting the file fails, the command prints the error and exits 1, as integration catalog remove does. The other catalog remove commands are not affected: presets, workflows and workflow steps treat an empty list as no config, and bundles always add the built-in catalogs.

Testing

  • New test_extension_catalog_remove_final_entry_restores_defaults: add one catalog, remove it, then extension catalog list exits 0 and lists default and community, and the config file is gone. On main it fails with the error above (assert 1 == 0).
  • New test_extension_catalog_remove_final_entry_falls_back_to_user_config: with a catalog in ~/.specify/extension-catalogs.yml, removing the only project catalog makes extension catalog list show the user-level catalog. On main it fails the same way.
  • New test_extension_catalog_remove_final_entry_notes_env_catalog_precedence: with SPECKIT_CATALOG_URL set, the message names the variable, and extension catalog list shows the environment catalog.
  • New negative case test_extension_catalog_remove_final_entry_reports_unlink_failure: when deleting the config fails, the command exits 1 with Failed to delete .specify/extension-catalogs.yml: <reason> and leaves the file unchanged. Without the handler the OSError escapes the CLI.
  • New negative case test_extension_catalog_remove_keeps_config_with_remaining_entries: removing one of two entries keeps the file with the other entry unchanged.
  • tests/specify_cli with tests/test_extensions.py: 4883 passed, 2 skipped.
  • Reran the CLI steps above against this branch: .specify/ is left empty and extension catalog list exits 0 with the built-in stack.

Equivalent local checks: tests ran with .venv/bin/python -m pytest and the CLI with the venv's specify, on macOS.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

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 and tests. A separate review agent checked the branch, and the orchestrating agent (Claude Opus 5) 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.

Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
…oved

`specify extension catalog remove` wrote `catalogs: []` after dropping the
last entry and printed that built-in defaults would be used. The catalog
loader rejects an existing config with no entries, so every later
catalog-backed command (`catalog list`, `search`, `add`, `update`, `info`)
failed until the file was deleted by hand. Delete the file instead, as
`specify integration catalog remove` already does.

Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
@Chaoqi31
Chaoqi31 requested a review from mnriem as a code owner October 10, 2026 04:51
@mnriem

mnriem commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Please update the message to “Project catalog override removed; user configuration or built-in defaults will be used” and add a regression test confirming that removing the final project catalog falls back to user-level configuration when present.

… removed

Removing the last project catalog deletes .specify/extension-catalogs.yml,
so the user-level ~/.specify/extension-catalogs.yml applies when it exists
and the built-in stack applies otherwise. Say so in the message, and pin
the user-level fallback with a test.

Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
@Chaoqi31

Copy link
Copy Markdown
Contributor Author

Done in f742f8a.

The message now reads "Project catalog override removed; user configuration or built-in defaults will be used."

The new test_extension_catalog_remove_final_entry_falls_back_to_user_config puts a catalog in ~/.specify/extension-catalogs.yml, then adds and removes the only project catalog. It checks that extension catalog list then shows the user-level catalog and Config: ~/.specify/extension-catalogs.yml. On main it fails because catalog list exits 1 on the leftover catalogs: []. tests/specify_cli passes (4417 passed, 2 skipped).

specify preset catalog remove prints the same "Built-in defaults will be used" line, although presets also fall back to ~/.specify/preset-catalogs.yml. I left it out to keep this PR to extensions. I can apply the same wording there in this PR if you want.

AI disclosure: Claude Code (Claude Opus 5), running autonomously for @Chaoqi31, wrote this change and this reply.

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

Filesystem deletion errors remain unhandled, and the success message is inaccurate when the environment override is active.

2 open findings
What changed in this PR

Fixes extension catalog removal so deleting the final project entry restores catalog fallback behavior.

Changes:

  • Deletes the empty project catalog configuration.
  • Adds built-in, user-level fallback, and remaining-entry tests.
File Description
src/​specify_cli/​extensions/​catalog/​command_remove.py Deletes the final-entry config and updates CLI messaging.
tests/​specify_cli/​extensions/​catalog/​test_command_remove.py Adds regression and fallback coverage.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/specify_cli/extensions/catalog/command_remove.py Outdated
Comment thread src/specify_cli/extensions/catalog/command_remove.py Outdated
@mnriem

mnriem commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

…ence

Deleting the config when the last project catalog is removed could raise
an OSError that escaped as a traceback. Print it as a CLI error and exit
1, as `integration catalog remove` does. SPECKIT_CATALOG_URL overrides
every catalog config, so when it is set, say that it still takes
precedence instead of promising user configuration or built-in defaults.

Assisted-by: Claude Code (model: Claude Opus 5, autonomous)
@Chaoqi31

Copy link
Copy Markdown
Contributor Author

@mnriem The new Copilot feedback is addressed in 05ea8d6. Details are in the two threads, and the description is updated.

AI disclosure: Claude Code (Claude Opus 5), running autonomously for @Chaoqi31, wrote this change and this reply.

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.

🟢 Approval recommended

The focused fix is consistent with catalog resolution behavior and has comprehensive regression coverage.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

@mnriem
mnriem merged commit 96774bc into github:main Oct 13, 2026
16 checks passed
@mnriem

mnriem commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

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.

3 participants