Skip to content

fix(workflows): quote-aware interpolation so a literal }} in a filter arg doesn't break multi-expression templates#3307

Open
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/interpolation-quote-aware-braces
Open

fix(workflows): quote-aware interpolation so a literal }} in a filter arg doesn't break multi-expression templates#3307
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/interpolation-quote-aware-braces

Conversation

@Quratulain-bilal

@Quratulain-bilal Quratulain-bilal commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

fixes #3306

what

evaluate_expression's multi-expression interpolation path still used the regex

_EXPR_PATTERN = re.compile(r"\{\{(.+?)\}\}")
return _EXPR_PATTERN.sub(_replacer, template)

the non-greedy (.+?)}} body stops at the first }} regardless of quoting, so a multi-expression template with a literal }} inside a quoted filter argument captured a truncated block body and raised ValueError from the filter parser.

{{ inputs.name }}: {{ inputs.missing | default('}}') }} raised instead of returning Bob: }}.

the single-expression fast path was already hardened for exactly this in #3208 / #3228 (_is_single_expression scans for a }} outside string literals). this brings the interpolation path to the same handling.

how

replaced _EXPR_PATTERN.sub with _interpolate_expressions, which walks the template and, for each {{ ... }} block, finds the closing }} outside string literals - reusing the same quote-scan _is_single_expression already uses. a literal }} in a plain resolved value (not an expression) passes through unchanged, and an unterminated {{ is left verbatim, matching the old regex behavior.

tests

  • test_multi_expression_with_literal_close_brace_in_argument - }} in the second block and in the first block. fails on the current code with ValueError, passes with the fix.
  • test_multi_expression_with_literal_open_brace_in_argument - {{ guard.
  • full TestExpressions (36) and full tests/test_workflows.py (363) pass.

verified the regression test fails on pre-fix code by reverting only the source change and re-running (raised ValueError on expressions.py:480), then confirmed it passes with the fix restored.

… arg doesn't break multi-expression templates

github#3208/github#3228 hardened the single-expression fast path (_is_single_expression)
so a literal {{ or }} inside a string argument like `| default('}}')` stays on
the typed path. the multi-expression interpolation path was left on the old
_EXPR_PATTERN regex, whose non-greedy `(.+?)}}` body stops at the first }}
regardless of quoting. so a multi-expression template with a literal }} in any
block captured a truncated body, hit the filter parser malformed, and raised
ValueError.

e.g. `{{ inputs.name }}: {{ inputs.missing | default('}}') }}` raised instead
of interpolating.

replace _EXPR_PATTERN.sub with _interpolate_expressions, which scans each block
for a }} outside string literals - the same quote handling _is_single_expression
already uses. plain-value passthrough (a literal }} in a resolved value, not an
expression) is unchanged.

add regression tests for a literal }} in the second block and in the first
block, plus a literal {{ guard.

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 workflow template interpolation for multi-expression strings by replacing the regex-based {{ ... }} substitution with a quote-aware scan, preventing a literal }} inside a quoted filter argument (e.g. default('}}')) from prematurely terminating an expression block.

Changes:

  • Replaced _EXPR_PATTERN.sub(...) multi-expression interpolation with _interpolate_expressions(...) that finds closing }} outside string literals.
  • Added regression tests covering literal }} and literal {{ inside quoted filter arguments in multi-expression templates.
Show a summary per file
File Description
src/specify_cli/workflows/expressions.py Implements quote-aware multi-expression interpolation to avoid truncating blocks when }} appears inside quoted arguments.
tests/test_workflows.py Adds regression tests for }} / {{ literals inside quoted filter args across multi-expression templates.

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: 1
  • Review effort level: Low

Comment on lines +225 to +229
if close == -1:
# No closing delimiter: leave the unterminated ``{{`` verbatim, the
# same way the regex leaves an unmatched opener untouched.
out.append(template[start:])
break

@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

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: multi-expression templates raise ValueError on a literal }} inside a filter argument

3 participants