Skip to content

Commit ed5402b

Browse files
jawwad-aliclaude
andcommitted
fix(workflows): guard non-mapping 'inputs:' block in engine._resolve_inputs
execute()/resume() run UNVALIDATED definitions (load_workflow does not validate). WorkflowDefinition stores `inputs` raw, so a non-mapping `inputs:` block (bare `inputs:` -> None, or `inputs: []`) crashed _resolve_inputs at `for name, input_def in definition.inputs.items()` with AttributeError, aborting the whole run. Return {} when inputs is not a mapping, mirroring validate_workflow's own `isinstance(definition.inputs, dict)` check. Protects both call sites (execute and resume); normal dict resolution is unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9b3546f commit ed5402b

2 files changed

Lines changed: 25 additions & 0 deletions

File tree

‎src/specify_cli/workflows/engine.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,14 @@ def _resolve_inputs(
14001400
) -> dict[str, Any]:
14011401
"""Resolve workflow inputs against definitions and provided values."""
14021402
resolved: dict[str, Any] = {}
1403+
# execute()/resume() accept UNVALIDATED definitions (load_workflow does
1404+
# not validate). A non-mapping ``inputs:`` block (bare ``inputs:`` ->
1405+
# None, or ``inputs: []``) is stored raw, so iterating ``.items()`` here
1406+
# would crash the run with AttributeError. Treat a non-mapping inputs
1407+
# block as "no inputs"; validate_workflow reports the malformed shape
1408+
# via its own isinstance check.
1409+
if not isinstance(definition.inputs, dict):
1410+
return {}
14031411
for name, input_def in definition.inputs.items():
14041412
if not isinstance(input_def, dict):
14051413
continue

‎tests/test_workflows.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3590,6 +3590,23 @@ def test_from_string(self, sample_workflow_yaml):
35903590
assert definition.id == "test-workflow"
35913591
assert len(definition.inputs) == 2
35923592

3593+
@pytest.mark.parametrize(
3594+
"block",
3595+
[
3596+
"workflow:\n id: w\n name: W\nsteps: []\ninputs: []\n", # list
3597+
"workflow:\n id: w\n name: W\nsteps: []\ninputs:\n", # null
3598+
],
3599+
)
3600+
def test_resolve_inputs_tolerates_non_mapping_inputs(self, block):
3601+
# execute()/resume() run UNVALIDATED definitions; a non-mapping `inputs:`
3602+
# block (list/null) is stored raw and would crash _resolve_inputs at
3603+
# `.items()`. It must be treated as "no inputs" instead.
3604+
from specify_cli.workflows.engine import WorkflowDefinition, WorkflowEngine
3605+
3606+
definition = WorkflowDefinition.from_string(block)
3607+
resolved = WorkflowEngine()._resolve_inputs(definition, {}) # must not raise
3608+
assert resolved == {}
3609+
35933610
def test_from_string_invalid(self):
35943611
from specify_cli.workflows.engine import WorkflowDefinition
35953612

0 commit comments

Comments
 (0)