fix(workflows): shell step validate() rejects non-string run (#3348)

ShellStep.validate() only checked that 'run' was present, so run: (null)
or a GitHub-Actions-style list validated clean; execute() then
str()-coerces the value and invokes it under shell=True, literally
running 'None' or "['echo', 'hi']" as a command. Add a type check after
the presence check, mirroring the command-step (#3262) and gate options
validation. Expression strings ('{{ ... }}') are strings, so they stay
valid.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ali jawwad
2026-07-08 00:48:26 +05:00
committed by GitHub
parent 0151d239b5
commit fb796c2a39
2 changed files with 30 additions and 0 deletions

View File

@@ -1266,6 +1266,26 @@ class TestShellStep:
errors = step.validate({"id": "test"})
assert any("missing 'run'" in e for e in errors)
@pytest.mark.parametrize("bad_run", [None, ["echo", "hi"], 42])
def test_validate_rejects_non_string_run(self, bad_run):
"""A non-string 'run' must be rejected at validation.
execute() str()-coerces run and invokes it under shell=True, so a
null or list run would otherwise run the Python repr as a command.
"""
from specify_cli.workflows.steps.shell import ShellStep
step = ShellStep()
errors = step.validate({"id": "s", "run": bad_run})
assert any("'run' must be a string" in e for e in errors)
def test_validate_accepts_string_and_expression_run(self):
from specify_cli.workflows.steps.shell import ShellStep
step = ShellStep()
assert step.validate({"id": "s", "run": "echo hi"}) == []
assert step.validate({"id": "s", "run": "{{ steps.x.output }}"}) == []
def test_output_format_json_exposes_data(self, tmp_path):
from specify_cli.workflows.steps.shell import ShellStep