Repository navigation
Conversation
…t unsupported syntax
When the expression evaluator met syntax it did not parse, it treated
the text as a variable path, which resolved to None, so a condition
using such syntax quietly gave the wrong result. Four confirmed cases:
exponent-form numbers (1e3), backslash-escaped quotes in string
literals ('it\'s'), chained indexing (matrix[0][1]), and word operators
without surrounding spaces ('x'and inputs.b).
Make each case either evaluate the way Python and Jinja2 do or fail
loudly, in line with the module's documented contract:
- Numeric literals parse with int() first, then float() for
digit-bearing text, so 1e3 is 1000.0; the words float() also accepts
(inf, nan) still resolve as variable names. _coerce_number applies
the same rule, so a numeric string in exponent form compares as a
number, matching workflow input coercion.
- Indexed path segments accept a chain of indices, so matrix[0][1]
walks both. An out-of-range index is missing data, not syntax, and
still resolves to None.
- Text that is neither a literal nor a resolvable variable path raises
ValueError naming the unsupported syntax, the way mis-wired filters
already do, with a hint for the two common shapes (escaped quote,
unspaced word operator). Validation probes are exempt, so authoring
diagnostics and their messages are unchanged.
Fixes github#5073
Assisted-by: Claude Code (model: Claude Fable 5, supervised)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #5073.
When the workflow expression evaluator met syntax it did not parse, it treated the text as a variable path, which resolved to
None— so a condition using such syntax quietly gave the wrong result. The issue confirmed four cases: exponent-form numbers (1e3), backslash-escaped quotes in string literals ('it\'s'), chained indexing (matrix[0][1]), and word operators without surrounding spaces ('x'and inputs.b).This PR makes each case either evaluate the way Python and Jinja2 do or fail loudly, in line with the module's documented contract ("a safe Jinja2 subset") and the precedent of mis-wired filters, which already raise
ValueError:int()first, thenfloat()for digit-bearing text, so{{ 1e3 }}is1000.0. The wordsfloat()also accepts (inf,nan) carry no digit and still resolve as variable names._coerce_numberapplies the same rule, so{{ inputs.s > 100 }}withs = "1e3"is true — matching what workflow input coercion already accepts as a number, so the same text no longer means different things in different places._INDEXED_SEGMENTaccepts a chain of indices and_resolve_dot_pathwalks them, so{{ steps.plan.output.matrix[0][1] }}is"b". An out-of-range index is missing data, not syntax, and still resolves toNone. The diagnostics gate reads the shared pattern, so validation follows for free (the existing wiring test pins this).ValueErrornaming the unsupported syntax, with a targeted hint for the two common shapes: an escaped quote in a string literal (quote it with the other quote type) and an unspaced word operator (and/or/in/notneed a space on both sides). A well-formed path that merely finds nothing (inputs.missing) still resolves toNone: missing data is not a syntax fault.Validation probes (
_collect_leaves,_evaluator_rejects) are exempt from the new raise, so the authoring diagnostics and their messages are unchanged — the gate still owns validation-time wording, and all existing diagnostics tests pass unmodified except the five that deliberately pinned the old evaluator behavior through the gate (they now pin the new behavior, which is exactly the drift-detection those pins exist for).The docs gain a note in the expression section describing the new error and the newly supported forms.
Testing
Three new regression tests in
TestExpressions(one per issue theme) fail onmainexactly as the issue describes —{{ 1e3 }}isNone,matrix[0][1]isNone, escaped quotes and unspaced operators silently yieldNone/False— and pass with this change.The reproduction script from [Bug]: Unsupported expression syntax evaluates to None silently instead of raising #5073 now prints the expected value for every case: cases 1 and 3 evaluate as in Jinja2, cases 2 and 4 raise
ValueErrornaming the syntax, and input coercion of"1e3"agrees with expression evaluation.tests/test_workflows.py+tests/unit/test_condition_expression_block.py+tests/workflows: 1,808 pass locally (.venv/bin/python -m pytestper CONTRIBUTING).Full suite: 13,650 passed; the same 11 pre-existing failures in the script-parity tests (
test_*_python_parity.py) occur on a cleanmaincheckout on this machine and each passes in isolation on both branches (also noted in fix(workflows): evaluate chained comparisons pairwise like Python #5203).Tested locally with
uv run specify --helpRan existing tests with
uv sync && uv run pytestTested with a sample project (if applicable)
AI Disclosure
AI disclosure: Written with Claude Code (model: Claude Fable 5, default reasoning effort, human-supervised). The agent located the defects, wrote the fix, tests and docs note, and ran the verification (failing-on-main tests, issue repro script, full-suite comparison against clean
main); I reviewed the diff and the evidence before submitting. The commit carries a matchingAssisted-by:trailer.🤖 Generated with Claude Code