fix(workflows): validate non-string step types (#4111)

Return an actionable validation error when a workflow step type is a YAML list or mapping instead of raising during registry membership checks.

Assisted-by: OpenAI Codex (model: GPT-5, autonomous)
This commit is contained in:
Ngo Quoc Viet
2026-08-14 21:18:25 +07:00
committed by GitHub
parent 672f812927
commit d6e09a17c4
2 changed files with 34 additions and 0 deletions
+11
View File
@@ -366,6 +366,17 @@ def _validate_steps(
# Determine step type
step_type = step_config.get("type", "command")
if not isinstance(step_type, str):
# Registry keys are strings. Checking an unhashable YAML value
# (for example ``type: [shell]`` or a mapping) against the set
# below raises a raw TypeError before validation can report the
# authoring mistake. Guard every non-string shape first, matching
# the typed validation already applied to workflow and step IDs.
errors.append(
f"Step {step_id!r}: 'type' must be a string, got "
f"{type(step_type).__name__} ({step_type!r})."
)
continue
if step_type not in _get_valid_step_types():
errors.append(
f"Step {step_id!r} has invalid type {step_type!r}."
+23
View File
@@ -4565,6 +4565,29 @@ steps:
errors = validate_workflow(definition)
assert any("invalid type" in e.lower() for e in errors)
@pytest.mark.parametrize("step_type", [["shell"], {"name": "shell"}])
def test_non_string_step_type_reports_error(self, step_type):
"""Unhashable YAML values must not crash registry membership checks."""
from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow
definition = WorkflowDefinition(
{
"workflow": {
"id": "test",
"name": "Test",
"version": "1.0.0",
},
"steps": [{"id": "bad", "type": step_type}],
}
)
errors = validate_workflow(definition)
assert errors == [
f"Step 'bad': 'type' must be a string, got "
f"{type(step_type).__name__} ({step_type!r})."
]
def test_nested_step_validation(self):
from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow