Skip to content

fix(extensions): reject aliases that shadow core commands - #4558

Open
chelsealong wants to merge 2 commits into
github:mainfrom
chelsealong:fix/4555-alias-shadows-core-command
Open

chelsealong wants to merge 2 commits into
github:mainfrom
chelsealong:fix/4555-alias-shadows-core-command

Conversation

@chelsealong

Copy link
Copy Markdown
Contributor

Summary

Fixes #4555.

_validate_install_conflicts is documented as rejecting installs that "would shadow core or installed extension commands", but _get_installed_command_name_map only walks self.registry — installed extensions. Core command names were never in the map it consults, so an extension could declare a core command's fully-qualified name (e.g. speckit.taskstoissues) as an alias and install successfully, shadowing the core command.

Primary command names are already namespace-checked against CORE_COMMAND_NAMES in _collect_manifest_command_names (a primary name like speckit.taskstoissues is correctly rejected — it fails the required two-segment speckit.{extension}.{command} pattern). Aliases are intentionally free-form (existing community extensions use short aliases like speckit.verify), so they can't be pattern-checked the same way — the only safe fix is to compare declared alias names directly against the fully-qualified core command names in the install-conflict check.

Fix

In _validate_install_conflicts (src/specify_cli/extensions/__init__.py), build core_command_names from CORE_COMMAND_NAMES (qualified as speckit.<name>) and reject any declared command/alias name that matches, in addition to the existing installed-extension check.

Test plan

  • Added test_install_rejects_alias_shadowing_core_command to tests/test_extensions.py, reproducing the exact scenario from the issue: a manifest declaring speckit.probe.taskstoissues with alias speckit.taskstoissues (a live core command).
  • Fail-before / pass-after: reverted just the source change (git checkout HEAD~1 -- src/specify_cli/extensions/__init__.py), confirmed the new test fails (Failed: DID NOT RAISE ValidationError), restored the fix, confirmed it passes.
  • Full tests/test_extensions.py: 536 passed.
  • Full suite (python -m pytest tests -q): 8054 passed, 12 skipped, no failures.

AI Disclosure

  • I did use AI assistance (describe below)

This fix was implemented with Claude Code (Claude Sonnet 5), based on the reproduction and root-cause analysis in the linked issue. I reviewed the code paths involved (_collect_manifest_command_names, _get_installed_command_name_map, _validate_install_conflicts), confirmed the fail-before/pass-after test behavior described above, and ran the full test suite myself before opening this PR.

🤖 Generated with Claude Code

_validate_install_conflicts only compared declared command/alias names
against installed extensions, never against core command names, so an
extension could claim a core command's fully-qualified name (e.g.
'speckit.taskstoissues') as an alias and shadow it silently. Primary
names are already namespace-checked against CORE_COMMAND_NAMES, but
aliases are intentionally free-form, so this can only be caught in the
install-conflict check by comparing against the qualified core names
directly.

Fixes github#4555
@chelsealong
chelsealong requested a review from mnriem as a code owner September 12, 2026 11:24
@mnriem mnriem added the triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review label Sep 12, 2026
@mnriem
mnriem requested a balanced review from Copilot September 15, 2026 16:13
@mnriem mnriem added triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate and removed triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review labels Sep 15, 2026

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

Equivalent plain or hyphenated aliases can still shadow core commands after agent-specific name transformation.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Prevents extension aliases from shadowing core commands.

Changes:

  • Adds core-name collision validation.
  • Adds a regression test for speckit.taskstoissues.
  • Fail-before evidence was reported but not independently executed.
File summaries
File Description
src/specify_cli/extensions/__init__.py Checks declared names against core commands.
tests/test_extensions.py Tests canonical core-alias rejection.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Balanced

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

Comment thread src/specify_cli/extensions/__init__.py Outdated
installed_names = self._get_installed_command_name_map(
exclude_extension_id=manifest.id
)
core_command_names = {f"speckit.{name}" for name in CORE_COMMAND_NAMES}
Comment thread src/specify_cli/extensions/__init__.py Outdated
]
if collisions:
raise ValidationError(
"Extension commands conflict with installed extensions:\n- "
…adow core commands

Copilot review on github#4558 pointed out that agent-specific output-name
normalization (CommandRegistrar._compute_output_name, and the
Cline/Forge/Junie formatters) collapses speckit.taskstoissues,
taskstoissues, and speckit-taskstoissues to the same on-disk command
name, so the exact-dotted-string check missed the plain and hyphenated
alias spellings. Also fixed the error heading, which said "conflict
with installed extensions" even for a core-only collision.
@chelsealong

Copy link
Copy Markdown
Contributor Author

Addressed both review comments in 4a6ac69:

  • Added _normalize_shadow_name and used it to compare declared alias names against core commands using their on-disk output form (dots → hyphens, speckit- prefix), matching the normalization CommandRegistrar._compute_output_name and the Cline/Forge/Junie formatters apply. This catches taskstoissues and speckit-taskstoissues, not just the exact speckit.taskstoissues spelling. Added a parametrized regression test for both forms, confirmed it fails without the fix (DID NOT RAISE ValidationError) and passes with it.
  • Fixed the error heading to say "conflict with core or installed extension commands" instead of implying only installed extensions can collide.

Full suite: 8056 passed, 12 skipped. ruff check src tests: clean.

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

Labels

triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An extension alias can shadow a core command; _validate_install_conflicts only checks installed extensions

3 participants