Skip to content

fix(extensions): reject hook commands that are not strings - #5146

Merged
mnriem merged 2 commits into
github:mainfrom
kartsan03:fix/hook-command-must-be-string
Oct 28, 2026
Merged

mnriem merged 2 commits into
github:mainfrom
kartsan03:fix/hook-command-must-be-string

Conversation

@kartsan03

@kartsan03 kartsan03 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

ExtensionManifest only checks that a hook's command is truthy, so command: [speckit.test-ext.hello] or a mapping passes validation. specify extension add then fails in HookExecutor.register_hooks with TypeError: unhashable type: 'list'. By then the extension directory and the agent skill are already written, so the user gets a raw traceback and leftover files, with no registry entry.

Extension events already reject this: events/__init__.py requires a non-blank string command (validate_events()). This PR applies the same check to hooks, so the manifest fails with a ValidationError before anything is installed. EXTENSION-API-REFERENCE.md already documents the field as command: string.

Repro with the real CLI and the manifest from the issue:

specify init --here --integration claude --script sh --ignore-agent-tools
specify extension add --dev ../test-ext
main (222a973) This PR
exit code 1 1
output traceback ending in TypeError: unhashable type: 'list' Validation Error: Hook 'after_tasks' missing required 'command' string
left on disk .specify/extensions/test-ext/, .claude/skills/speckit-test-ext-hello/SKILL.md nothing

Refs #5080. This covers the reported case only. The issue also notes that the install rollback only cleans up for the generic agent; I left that alone because #4959 reworks the install transaction.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

New tests:

  • tests/test_extensions.py::TestExtensionManifest::test_hook_command_must_be_a_string: list, mapping, integer, boolean and blank commands, each as a single mapping and in a list of entries, are rejected.
  • tests/specify_cli/extensions/test_command_add.py::TestExtensionAddCLI::test_add_dev_rejects_invalid_hook_command_before_writing_files[list|mapping]: extension add --dev exits 1 with no traceback, and leaves no extension directory, agent files or registry entry.

With extensions/__init__.py reverted to main, all 10 manifest cases fail (DID NOT RAISE), and both CLI cases fail on TypeError("unhashable type: 'list'") / 'dict'.

Full suite on Linux, Python 3.13: 13179 passed, 435 skipped (uv sync --extra test, then pytest -n 3, 6 min). Ruff 0.15.0 (ruff check src tests) passes. I haven't run it on Windows.

AI Disclosure

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

AI disclosure: Codex CLI (model: gpt-6.1-sol, reasoning effort xhigh, autonomous, offline sandbox) reproduced the issue on main and wrote a first version of the fix and tests. Claude Code (model: claude-opus-5-5, autonomous) narrowed it to the command check, matched it to the existing events rule, reran the repro with the real CLI on main and the branch, ran the full suite and lint, and drafted this description. The commit carries Assisted-by: trailers for both.

`ExtensionManifest` only checked that a hook's `command` was truthy, so
`command: [speckit.x.hello]` or a mapping passed validation. `extension add`
then crashed in `HookExecutor.register_hooks` with
`TypeError: unhashable type: 'list'`, after the extension directory and the
agent skill had already been written.

Apply the rule extension events already use (github#17): a hook command must be a
non-blank string, so the manifest is rejected with a `ValidationError`
before anything is installed.

Refs github#5080

Assisted-by: Codex CLI (model: gpt-6.1-sol, autonomous)
Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
@kartsan03
kartsan03 requested a review from mnriem as a code owner October 27, 2026 22:52
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Oct 28, 2026
@mnriem
mnriem requested a max review from Copilot October 28, 2026 13:29

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

An open review nit flags that (#17) points to an unrelated Gemini CLI issue rather than the relevant validate_events() check.

1 open issue
What changed in this PR

This PR rejects malformed hook commands during extension manifest validation, preventing the installation crash and leftover files reported in #5080.

Changes:

  • Require hook commands to be non-blank strings, matching event validation.
  • Add parameterized manifest tests for invalid commands in both supported hook shapes.
  • Add CLI regression tests verifying clean failure without installed files or registry entries.

The PR provides before-and-after regression results; tests were not rerun during this review.

File Description
tests/​test_extensions.py Tests rejection of non-string and blank hook commands.
tests/​specify_cli/​extensions/​test_command_add.py Verifies invalid hooks fail before installation writes files.
src/​specify_cli/​extensions/​__init__.py Validates hook command type and non-blank content.

Review effort: Max

Comment thread src/specify_cli/extensions/__init__.py Outdated
@mnriem

mnriem commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

@mnriem mnriem added the author-awaiting Waiting on author response label Oct 28, 2026
`github#17` read as a GitHub issue reference, but that issue is about Gemini CLI.

Refs github#5080

Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
@kartsan03

Copy link
Copy Markdown
Contributor Author

Fixed in b6d14f3. The comment now points to validate_events() in events/__init__.py instead of #17, and so does the PR description. It's a comment-only change. tests/test_extensions.py and tests/specify_cli/extensions/test_command_add.py pass, and Ruff 0.15.0 passes.

Drafted on behalf of @kartsan03 by Claude Code (model: claude-opus-5-5, autonomous).

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

No active review issues are reported for the change that rejects malformed extension hook commands before installation.

No open issues 🎉

1 resolved since last review

Review effort: Max

@mnriem
mnriem merged commit a83d4cf into github:main Oct 28, 2026
16 checks passed
@mnriem

mnriem commented Oct 28, 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

author-awaiting Waiting on author response triage-must-have Verdict: high-value, important work for Spec Kit — do first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants