mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(workflows): command/prompt steps fail cleanly on a non-string integration (#3626)
_try_dispatch guarded only 'if not integration_key', then called
get_integration(integration_key). A non-string integration (a list/dict, or an
expression like integration: "{{ steps.pick.output.agents }}" that resolves to a
list) reached the registry dict lookup and raised 'TypeError: unhashable type:
list', aborting the entire workflow run. Widen the guard to also require a str,
so a non-string integration is treated as not-dispatchable and execute() falls
through to its existing FAILED StepResult (unconfigured integration=None still
returns None as before). Applied to both command and prompt steps.
Tests: a list integration now yields a FAILED result (fail before: TypeError).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -189,7 +189,11 @@ class CommandStep(StepBase):
|
||||
not possible (integration not found, CLI not installed, or
|
||||
dispatch not supported).
|
||||
"""
|
||||
if not integration_key:
|
||||
if not integration_key or not isinstance(integration_key, str):
|
||||
# A non-string integration (a list/dict/expression that resolved to
|
||||
# one) would raise TypeError: unhashable type from get_integration's
|
||||
# dict lookup below and abort the whole run. Treat it as "not
|
||||
# dispatchable" so execute() falls through to its FAILED StepResult.
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
@@ -138,7 +138,10 @@ class PromptStep(StepBase):
|
||||
context: StepContext,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Dispatch *prompt* directly through the integration CLI."""
|
||||
if not integration_key or not prompt:
|
||||
if not integration_key or not isinstance(integration_key, str) or not prompt:
|
||||
# A non-string integration would raise TypeError: unhashable type
|
||||
# from get_integration's dict lookup and abort the run; treat it as
|
||||
# not dispatchable so execute() falls through to its FAILED result.
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
@@ -1162,6 +1162,21 @@ class TestCommandStep:
|
||||
result = step.execute(config, ctx)
|
||||
assert result.output["integration"] == "gemini"
|
||||
|
||||
def test_execute_non_string_integration_fails_cleanly(self):
|
||||
"""A non-string integration (e.g. a list from an expression that resolved
|
||||
to one) must FAIL the step cleanly, not crash the run with
|
||||
'TypeError: unhashable type: list' from get_integration's dict lookup."""
|
||||
from specify_cli.workflows.steps.command import CommandStep
|
||||
from specify_cli.workflows.base import StepContext, StepStatus
|
||||
|
||||
step = CommandStep()
|
||||
config = {
|
||||
"id": "s", "command": "speckit.plan",
|
||||
"integration": ["claude"], "input": {},
|
||||
}
|
||||
result = step.execute(config, StepContext())
|
||||
assert result.status == StepStatus.FAILED
|
||||
|
||||
def test_step_override_model(self):
|
||||
from unittest.mock import patch
|
||||
from specify_cli.workflows.steps.command import CommandStep
|
||||
@@ -1359,6 +1374,20 @@ class TestPromptStep:
|
||||
assert result.output["integration"] == "claude"
|
||||
assert result.output["dispatched"] is False
|
||||
|
||||
def test_execute_non_string_integration_fails_cleanly(self):
|
||||
"""A non-string integration must FAIL the step cleanly, not crash with
|
||||
'TypeError: unhashable type: list' from get_integration's dict lookup."""
|
||||
from specify_cli.workflows.steps.prompt import PromptStep
|
||||
from specify_cli.workflows.base import StepContext, StepStatus
|
||||
|
||||
step = PromptStep()
|
||||
config = {
|
||||
"id": "p", "type": "prompt", "prompt": "do it",
|
||||
"integration": ["claude"],
|
||||
}
|
||||
result = step.execute(config, StepContext())
|
||||
assert result.status == StepStatus.FAILED
|
||||
|
||||
def test_execute_with_step_integration(self):
|
||||
from unittest.mock import patch
|
||||
from specify_cli.workflows.steps.prompt import PromptStep
|
||||
|
||||
Reference in New Issue
Block a user