[Bug]: A hook with a list-valued `command` passes manifest validation, then crashes `extension add` and leaves files behind

Abierto Apto para principiantes
#5,080 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
78/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
cli

Línea de trabajo

Empieza en src/specify_cli/extensions/__init__.py y lee ExtensionManifest._validate alrededor de la línea 435 y la ruta de registro del hook install_from_directory alrededor de la línea 3084. Compara la validación del hook con el tipo string documentado en extensions/EXTENSION-API-REFERENCE.md; reproduce el problema con el script proporcionado y revisa las pruebas de validación de extensiones. El trabajo estará terminado cuando un comando de hook que no sea una cadena provoque ValidationError antes de la instalación y no quede ningún archivo.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug-assess invalid triage-must-have
Bug Description

ExtensionManifest checks only that a hook's command is truthy (src/specify_cli/extensions/__init__.py:435), so a list such as command: [speckit.test-ext.hello] is accepted. During install, HookExecutor.register_hooks uses the command as a dictionary key (__init__.py:5872), which raises TypeError: unhashable type: 'list'.

By then the extension directory has been copied and the agent skill has been written. The except handler in install_from_directory only rolls back when the generic agent is active (__init__.py:3132). With any other agent the files stay on disk, and no registry entry is written. The TypeError is not one of the exceptions specify extension add handles, so the user sees a raw traceback.

Steps to Reproduce

Save this as repro.py and run uv run python -I repro.py from the repository root.

import os, tempfile, traceback
from pathlib import Path
from typer.testing import CliRunner
from specify_cli import app

root = Path(tempfile.mkdtemp())
ext = root / "test-ext"
(ext / "commands").mkdir(parents=True)
(ext / "commands/hello.md").write_text("---\ndescription: hi\n---\n\nHello $ARGUMENTS\n")
(ext / "extension.yml").write_text("""schema_version: "1.0"
extension: {id: test-ext, name: Test, version: 1.0.0, description: d}
requires: {speckit_version: ">=0.1.0"}
provides:
  commands:
    - {name: speckit.test-ext.hello, file: commands/hello.md, description: x}
hooks:
  after_tasks:
    command: [speckit.test-ext.hello]
""")
proj = root / "project"
(proj / ".specify").mkdir(parents=True)
(proj / ".claude/skills").mkdir(parents=True)
os.chdir(proj)

r = CliRunner().invoke(app, ["extension", "add", "--dev", str(ext)])
print("exit code:", r.exit_code)
print("".join(traceback.format_exception(r.exception)[-3:]))
print("registry:", (proj / ".specify/extensions/.registry").read_text()
      if (proj / ".specify/extensions/.registry").exists() else "<none>")
print("left on disk:", sorted(str(p.relative_to(proj)) for p in proj.rglob("*") if "test-ext" in str(p)))
Expected Behavior

The manifest is rejected with a ValidationError and nothing is installed. extensions/EXTENSION-API-REFERENCE.md:72 documents the field as command: string. The ExtensionManifest.__init__ docstring says it "Raises: ValidationError: If manifest is invalid" (__init__.py:243-244).

Actual Behavior
exit code: 1
registry: <none>
left on disk: ['.claude/skills/speckit-test-ext-hello', '.claude/skills/speckit-test-ext-hello/SKILL.md', '.specify/extensions/test-ext', '.specify/extensions/test-ext/.specify-dev', '.specify/extensions/test-ext/.specify-dev/agent-commands', '.specify/extensions/test-ext/.specify-dev/agent-commands/claude', '.specify/extensions/test-ext/.specify-dev/agent-commands/claude/speckit-test-ext-hello', '.specify/extensions/test-ext/.specify-dev/agent-commands/claude/speckit-test-ext-hello/SKILL.md', '.specify/extensions/test-ext/commands', '.specify/extensions/test-ext/commands/hello.md', '.specify/extensions/test-ext/extension.yml']

The command exits 1. The extension is not in the registry, but .claude/skills/speckit-test-ext-hello/SKILL.md and .specify/extensions/test-ext/ remain on disk.

Specify CLI Version

1.2.4.dev0 (main at 27d14e2)

AI Agent

Claude Code

Operating System

Linux 6.18 (Ubuntu container)

Python Version

Python 3.12.3

Error Logs
  File "<checkout>/src/specify_cli/extensions/__init__.py", line 3084, in install_from_directory
    hook_executor.register_hooks(manifest)
  File "<checkout>/src/specify_cli/extensions/__init__.py", line 5872, in register_hooks
    if command in new_entries:
       ^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
Additional Context

Hook command values of other non-string types (a mapping, for example) take the same path. The failure-path rollback only covers the generic agent, so any other failure after command registration (for example, an unwritable .specify/extensions.yml) is likely to leave the same leftovers. That was not tested separately. Rejecting non-string hook commands in _validate would fix the reported case.

Also reproduced on macOS (Darwin 25.6, arm64) with Python 3.14.4 and uv run python -I repro.py against 27d14e2; the output matches the above.

AI Disclosure

Found and reproduced with Claude Code (model: Claude Opus 4.8), run autonomously under human supervision. The human reporter re-ran every reproduction above before filing.

Lenguaje dominante
Python
Estrellas
147k
Forks
13.2k
Merge medio
1 d 4 h
PR fusionados (30 d)
219

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de github/spec-kit

Todos los issues de github/spec-kit

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.