[Bug]: Unsupported expression syntax evaluates to None silently instead of raising
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
- #5206 par @aarjang — fermée sans fusion
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 52/100
Piste de recherche
Start in src/specify_cli/workflows/expressions.py, especially the parser and evaluation points cited in the report: lines 304, 331, 715, 800, 884, 893, 930-976, and _safe_compare. Read the evaluator tests in tests/test_workflows.py and run the reproductions from the issue for exponent numbers, escaped quotes, chained indexing, and adjacent word operators. Done means each documented case evaluates as expected or raises a clear ValueError for unsupported syntax.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Bug Description
When the workflow expression evaluator meets syntax it does not parse, it does not raise. It treats the text as a variable path, which resolves to None (_resolve_dot_path, reached from src/specify_cli/workflows/expressions.py:969). A condition that uses such syntax then quietly gives the wrong result. The following cases were confirmed:
- Exponent-form numbers. Numeric literals and numeric strings are parsed with
float()only when they contain a., and otherwise withint()(expressions.py:930-935for literals,expressions.py:972-976for_coerce_number). So1e3is not read as a number: as a literal it is looked up as a variable and becomesNone, and the string"1e3"stays a string in comparisons. Workflow input coercion does accept"1e3"as the number1000(src/specify_cli/workflows/engine.py:1449-1456), so the same text means different things in different places. - Escaped quotes in string literals. A string literal is recognised only if its first matching quote is its last character (
expressions.py:800), so'it\'s'is looked up as a variable and becomesNone. As a result,inputs.name == 'it\'s'is false forname = "it's". - Chained indexing. The indexed-segment pattern accepts one index only (
expressions.py:304), somatrix[0][1]is looked up as the literal key"matrix[0][1]"(expressions.py:331) and returnsNone. - Word operators next to a quote.
and,orandinare only found with a space on both sides (expressions.py:884,expressions.py:893,expressions.py:715).inputs.a=='x'and inputs.bis therefore split only at==, its right side'x'and inputs.bis looked up as a variable and becomesNone, and the result is false.
Steps to Reproduce
- From a fresh clone of spec-kit, run
uv sync. - Save the script below as
repro.pyin the repository root. - Run
uv run python -I repro.py.
from specify_cli.workflows.base import StepContext
from specify_cli.workflows.engine import WorkflowEngine
from specify_cli.workflows.expressions import evaluate_condition, evaluate_expression
ctx = StepContext(
inputs={"n": 5, "s": "1e3", "name": "it's", "a": "x", "b": True},
steps={"plan": {"output": {"matrix": [["a", "b"], ["c", "d"]]}}},
)
cases = [
("1", "{{ 1e3 }}", "value"),
("1", "{{ inputs.n < 1e3 }}", "cond"),
("1", "{{ inputs.s > 100 }}", "cond"),
("2", r"{{ 'it\'s' }}", "value"),
("2", r"{{ inputs.name == 'it\'s' }}", "cond"),
("3", "{{ steps.plan.output.matrix[0] }}", "value"),
("3", "{{ steps.plan.output.matrix[0][1] }}", "value"),
("4", "{{ inputs.a == 'x' and inputs.b }}", "cond"),
("4", "{{ inputs.a=='x'and inputs.b }}", "cond"),
("4", "{{ 'x'in inputs.a }}", "cond"),
]
for num, expr, kind in cases:
fn = evaluate_expression if kind == "value" else evaluate_condition
print(f"[{num}] {expr} -> {fn(expr, ctx)!r}")
print("input coercion of '1e3' (type: number):",
WorkflowEngine._coerce_input("x", "1e3", {"type": "number"}))
Expected Behavior
The module docstring describes the evaluator as "a safe Jinja2 subset" (src/specify_cli/workflows/expressions.py:3), and _evaluate_simple_expression lists "String and numeric literals" as supported (expressions.py:791). For each case, the expression should either evaluate as it would in Jinja2 or raise a ValueError naming the unsupported syntax. Malformed filters already get such a ValueError (docs/reference/workflows.md, "Types are validated, not coerced"). Specifically:
{{ 1e3 }}should be1000.0, and{{ inputs.n < 1e3 }}should be true forn = 5.{{ inputs.s > 100 }}withs = "1e3"should be true, in line with the_safe_comparedocstring (expressions.py:1003-1012) and the input-coercion testtests/test_workflows.py:7092(("1e3", 1000)).{{ 'it\'s' }}should be"it's", and the comparison should be true.{{ steps.plan.output.matrix[0][1] }}should be'b'. The_resolve_dot_pathdocstring promises list indexing (expressions.py:312).{{ inputs.a=='x'and inputs.b }}should be true, like the spaced form.{{ 'x'in inputs.a }}should be true.
Actual Behavior
[1] {{ 1e3 }} -> None
[1] {{ inputs.n < 1e3 }} -> False
[1] {{ inputs.s > 100 }} -> False
[2] {{ 'it\'s' }} -> None
[2] {{ inputs.name == 'it\'s' }} -> False
[3] {{ steps.plan.output.matrix[0] }} -> ['a', 'b']
[3] {{ steps.plan.output.matrix[0][1] }} -> None
[4] {{ inputs.a == 'x' and inputs.b }} -> True
[4] {{ inputs.a=='x'and inputs.b }} -> False
[4] {{ 'x'in inputs.a }} -> False
input coercion of '1e3' (type: number): 1000
Specify CLI Version
1.2.4.dev0 (main at 27d14e2)
AI Agent
Not applicable
Operating System
Linux 6.18 (Ubuntu container)
Python Version
Python 3.12.3
Additional Context
When cases 1, 3 and 4 are used as an if condition, validate_workflow returns no errors. Case 2 is flagged in a condition, but as unbalanced delimiters and quotes rather than as an escaped quote.
Related: #5060 proposes replacing the hand-written expression parser with a grammar so that invalid input raises. It does not mention this case.
Also reproduced on macOS (Darwin 25.6, arm64) with Python 3.14.4 and uv run python -I repro.py against 27d14e2; the output matches the above.
AI Disclosure
Found and reproduced with Claude Code (model: Claude Opus 4.8), run autonomously under human supervision. The human reporter re-ran every reproduction above before filing.
- Langage dominant
- Python
- Étoiles
- 149k
- Forks
- 13.4k
- Merge moyen
- 1 j 5 h
- PR mergées (30 j)
- 223
Préparer son environnement
Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de github/spec-kit
-
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
Les mainteneurs répondent en général sous 1 jour
-
triage-must-have
Difficulté 1/5 Moins d'une heure Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour
-
Workflow-step verifier test fixture breaks when the checkout path contains whitespace (4 failures on macOS)Peut-être pris @mehdikarimi55 l’a pris il y a 3 jours. Ouvertetriage-can-wait
Difficulté 2/5 1-3 heures Accessibilité débutants 83/100
Les mainteneurs répondent en général sous 1 jour
-
Parity tests fail under the documented direct pytest invocation when the ambient python3 lacks PyYAML (11 failures)Peut-être pris @mehdikarimi55 l’a pris il y a 3 jours. Ouvertetriage-must-have
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
Les mainteneurs répondent en général sous 1 jour
-
bug triage-out-of-scope
Difficulté 2/5 1-3 heures Accessibilité débutants 64/100
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de github/spec-kit
Issues similaires
-
SslLibrary crashes with unhandled OSError: port/proto not found on non-standard SSL portsPeut-être pris @himanchalkaushale l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
OWASP/Nettacker#1884 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
A-connection-core C-enhancement
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 88/100
aws-samples/sample-per-model-bedrock#869 ·
Les mainteneurs répondent en général sous 1 jour
-
Spec: say that included_antigen_sources gates LENS labels; do not merge it into antigen kindsOuverte
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
Les mainteneurs répondent en général sous 1 jour
-
enhancement
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
fzn0x/watchtower#17 ·