Skip to content

fix: interpolate multi-expression templates instead of returning None (#3208)#3228

Open
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/3208-multi-expression-eval
Open

fix: interpolate multi-expression templates instead of returning None (#3208)#3228
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/3208-multi-expression-eval

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown

What

evaluate_expression returned None for any template containing two or more {{ }} blocks with no surrounding literal text, e.g. "{{ context.run_id }} {{ inputs.issue }}". Downstream this surfaced as the literal string "None" reaching commands (the reporter hit "no issue was provided" despite issue: "23" being in the run inputs).

Fixes #3208.

Root cause

The single-expression fast path used _EXPR_PATTERN.fullmatch(), but fullmatch defeats the pattern's non-greedy (.+?) body. For "{{ a }} {{ b }}" it still matches, expanding the body to capture everything between the first {{ and the last }} — i.e. "a }} {{ b". That garbage body fails dot-path resolution and returns None directly, bypassing the sub() interpolation path that would have resolved each expression separately.

Template fullmatch Result
{{ inputs.issue }} matches, body inputs.issue ✅ correct
bash x "{{ inputs.issue }}" no match (text prefix) → sub() ✅ correct
{{ a }} {{ b }} matches, body "a }} {{ b" None

How

Guard the fast path on stripped.count("{{") == 1 so only genuine single-expression templates take the typed-return path; multi-expression templates fall through to the existing sub() interpolation, which is already correct. Single-expression type preservation is unchanged.

Tests

Added two regression tests to TestExpressions:

  • test_multi_expression_no_surrounding_text"{{ context.run_id }} {{ inputs.issue }}""47c5eb4b 23"
  • test_multi_expression_adjacent_no_separator"{{ inputs.a }}{{ inputs.b }}""foobar"

Verified both fail on main (return None) and pass with the fix. All 31 TestExpressions tests pass; ruff clean. (The 5 symlink-test failures on my local Windows run pre-exist on main — a symlink-privilege limitation unrelated to this change; they pass on Linux CI.)

🤖 Generated with Claude Code

…github#3208)

`evaluate_expression` returned None for templates containing two or more
`{{ }}` blocks with no surrounding literal text, e.g.
`"{{ context.run_id }} {{ inputs.issue }}"`.

The single-expression fast path used `_EXPR_PATTERN.fullmatch()`, but
`fullmatch` defeats the pattern's non-greedy `(.+?)` body: for two adjacent
expressions it still matches, capturing everything between the first `{{`
and the last `}}` (`"context.run_id }} {{ inputs.issue"`) as the body. That
garbage failed dot-path resolution and returned None directly, bypassing the
`sub()` interpolation path that would have resolved each expression. Downstream
this surfaced as the literal string "None" reaching commands.

Guard the fast path on `stripped.count("{{") == 1` so only genuine
single-expression templates take the typed return; multi-expression templates
fall through to `sub()` and interpolate correctly.

Add regression tests for two expressions separated by a space and for adjacent
expressions with no separator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a workflow expression-evaluator edge case where templates containing multiple {{ ... }} blocks (and no non-whitespace surrounding text) could incorrectly take the single-expression fullmatch() fast path and return None, causing downstream commands to receive the literal string "None".

Changes:

  • Adjust evaluate_expression() to only use the typed single-expression fast path when the template is truly a single expression, otherwise fall back to interpolation.
  • Add regression tests covering multi-expression templates with whitespace-only separation and back-to-back expressions.
Show a summary per file
File Description
src/specify_cli/workflows/expressions.py Guards the typed fullmatch() fast path so multi-expression templates interpolate correctly instead of collapsing to None.
tests/test_workflows.py Adds regression tests to ensure multi-expression templates interpolate as expected.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +396 to +400
stripped = template.strip()
if stripped.count("{{") == 1:
match = _EXPR_PATTERN.fullmatch(stripped)
if match:
return _evaluate_simple_expression(match.group(1).strip(), namespace)
Comment thread tests/test_workflows.py Outdated

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: expression evaluator returns None for multi-expression templates without surrounding text

3 participants