fix(workflows): reject non-string 'condition' in if/while/do-while steps#3706
fix(workflows): reject non-string 'condition' in if/while/do-while steps#3706jawwad-ali wants to merge 3 commits into
Conversation
`if_then`, `while_loop`, and `do_while` validate() confirm `condition` is
present but never that it is a string. execute() feeds it to
`evaluate_condition()`, which returns a non-string as-is and takes `bool()`
of it -- so `condition: [1, 2]` (a list authoring mistake) silently resolves
to `True`, branching wrongly / spinning the loop to `max_iterations`, with no
error reported.
Reject a present-but-non-string `condition` at validation, mirroring the
existing prompt/shell/command 'must be a string' guards. `"true"`/`"false"`
and expressions like `"{{ ... }}"` are strings, so they stay valid.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds validation to prevent non-string workflow conditions from being silently truthiness-coerced.
Changes:
- Validates conditions for if, while, and do-while steps.
- Adds parameterized regression tests for invalid condition types.
Show a summary per file
| File | Description |
|---|---|
if_then/__init__.py |
Rejects non-string if conditions. |
while_loop/__init__.py |
Rejects non-string while conditions. |
do_while/__init__.py |
Rejects non-string do-while conditions. |
tests/test_workflows.py |
Covers invalid and valid condition values. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 4
- Review effort level: Medium
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
…split accurately Address review feedback: the guard comments attributed the non-string pass-through to evaluate_condition(), which always returns a bool. It is evaluate_expression() (called by evaluate_condition) that returns a non-string unchanged; evaluate_condition then applies bool() to that value. Reword all four sites (if/while/do-while guards + the mirror test comment) to name the two stages correctly. Comments only -- no behaviour change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Good catch — fixed in 6a78c02. You're right that
Comments only; no behaviour change. |
Self-review catch: the guard rejected EVERY non-string, which broke an input that previously worked. An unquoted ``condition: false`` is idiomatic YAML and resolves exactly today -- evaluate_expression passes the bool through and evaluate_condition's bool() is a no-op (verified: evaluate_condition(False) is False, (True) is True). The if/while steps even default ``condition`` to the bool ``False`` themselves, so bool is the field's natural type, not an authoring mistake. Accept (str, bool) and reject only the genuinely silent-coercion types (list/dict/int/float, e.g. condition: [1, 2] is always True). Message updated to "must be a string or boolean"; the bad-value tests drop True and gain 1.5, and each step gains a positive bool case. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem
IfThenStep,WhileStep, andDoWhileStepvalidate()confirm thatconditionis present but never that it is a string.At run time
execute()feedsconditiontoevaluate_condition():So a non-string condition is returned unchanged and
bool()-coerced. A list/dict/number authoring mistake silently resolves to a truthiness instead of erroring:condition: [1, 2]→bool([1, 2])→ always True → if-branch always taken / while-loop spins tomax_iterations.condition: {}→ always False, etc.No error is reported, so the mistake is invisible.
Fix
Reject a present-but-non-string
conditionin each of the three steps'validate(), mirroring the existingprompt/shell/command'must be a string' guards."true"/"false"and expressions like"{{ inputs.flag }}"are strings, so they remain valid — no behaviour change for correct workflows.Verification
[list, dict, int, bool]) across all three step classes: fail before the guard (12 failures — the non-string conditions pass validation), pass after.test_validate_accepts_string_conditionconfirms"true"/"false"/"{{ ... }}"still validate.TestIfThenStep/TestWhileStep/TestDoWhileStep: 53 passed.ruff checkclean on all changed files.AI-assisted: authored with Claude Code. I traced the runtime path (
evaluate_condition→bool()), confirmed the silent-coercion onmain, and verified the guards fail-before/pass-after.