#7559·skyvern

Renamed workflow identifiers are never rewritten inside {% %} statements or mid-expression Jinja references

Author: dev-synthiaCreated Jul 19, 2026Updated Jul 20, 2026
LabelsbugBackendWorkflowsWorkflow Copilot

What happened

replace_jinja_reference in skyvern/utils/templating.py only matches an identifier immediately after {{:

python
pattern = rf"\{{\{{(\s*){escaped_old_key}(?![a-zA-Z0-9_])"

So when the workflow import sanitizer (sanitize_workflow_yaml_with_references, runs on POST /workflows and POST /workflows/{id} with a yaml_definition) renames a parameter key or block label, references are only repaired when they sit at the head of a {{ ... }} expression. Two documented usage shapes are silently skipped:

  • Mid-expression references: {{ current_index < max attempts }} — the exact shape of a while-loop Condition, which the docs describe as a Jinja template with the reserved {{ current_index }} counter (docs/cloud/building-agents/configure-blocks.mdx). current_index is at the head; the parameter being compared against never is.
  • Statement references: anything inside {% if ... %} / {% for ... %} is never matched at all, including {label}_output guards like {% if my-block_output.success %}.

Impact

After a rename (invalid identifier or a collision-driven _2 suffix), the skipped references keep pointing at the old name. With default lax templating nothing errors at import or run time — conditions evaluate against undefined/wrong values, so loops mis-terminate and guards mis-fire. When the rename was collision-driven, the stale reference can silently bind to the other parameter that now owns the old name.

Reproduce

python
from skyvern.utils.templating import replace_jinja_reference

replace_jinja_reference("{{ current_index < max_attempts }}", "max_attempts", "max_attempts_2")
# returns unchanged; expected "{{ current_index < max_attempts_2 }}"

replace_jinja_reference("{% if my_flag %}go{% endif %}", "my_flag", "my_flag_2")
# returns unchanged; expected "{% if my_flag_2 %}go{% endif %}"

The same matcher backs every rewrite path in the sanitizer, including the sentinel-guarded _rewrite_error_code_mapping_refs_atomic, so all of them share the coverage gap.

Notes

This is the matching-coverage defect noted at the end of Skyvern-AI/skyvern#7554 (scoped out of #7555, which fixes the separate chaining problem). PR incoming that widens the matcher to full-token occurrences inside {{ ... }} and {% ... %} spans while leaving prose outside Jinja delimiters, attribute accesses, quoted string literals, and longer identifiers untouched.