Repository navigation
Restrict the second render pass in SqlToSlackWebhookOperator - #71402
Merged
Merged
Conversation
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
JelyFishhhhhh
force-pushed
the
fix-slack-double-render
branch
2 times, most recently
from
August 14, 2026 05:43
2e69115 to
dd2061f
Compare
The operator renders in two passes because slack_message references results_df, which does not exist until the query has run. The first pass renders every templated field except slack_message; the second is meant to fill in that one deferred field. The second pass rendered self.template_fields instead, so sql - already rendered by the first pass - was compiled again with its own rendered output as the template source. Jinja syntax arriving inside a context value was therefore evaluated on the second pass rather than staying literal. Restrict the second pass to slack_message. The first pass is unchanged and slack_message still renders exactly as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A mapped task's first render bypasses render_template_fields and leaves times_rendered at 0, so sending re-rendered the already-rendered sql. Render the deferred slack_message field directly instead of relying on the counter. Generated-by: Claude Opus 5
potiuk
force-pushed
the
fix-slack-double-render
branch
from
October 5, 2026 15:14
44a67ad to
3939cb3
Compare
A mapped task's first render also renders slack_message, so rendering the attribute again at send time evaluated Jinja that arrived in a context value. Keep the template given to the constructor and render that, once, when sending. Generated-by: Claude Opus 5
potiuk
approved these changes
Oct 5, 2026
potiuk
left a comment
Member
There was a problem hiding this comment.
Thanks! I approved after pushing two fixup commits for the mapped-task path (.partial().expand()):
- A mapped task's first render goes through
MappedOperator, which renders every template field and never incrementstimes_rendered. Soexecute()still took the "first render" branch and rendered the already-renderedsqlagain, with the same double evaluation this PR removes. - Sending now renders only
slack_message, once, from the template given to the constructor, so no field is rendered twice on either path..j2templates keep working, because the kept value is the file name, whichrender_templateloads as usual. - Added
test_send_renders_each_field_once_after_mapped_first_render, which reproduces the mapped first render with injected Jinja in bothsqlandslack_message.
Drafted-by: Claude Code (Opus 5.5); reviewed by @potiuk before posting
|
Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions. |
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.
What
SqlToSlackWebhookOperator.render_template_fieldsrenders in two passes, becauseslack_messageusually referencesresults_df, which does not exist until the query has run. The first pass renders every templated field exceptslack_message; the second, from inside_render_and_send_slack_message, is meant to fill in that one deferred field.The second pass rendered
self.template_fields— all of them, not just the deferred one:So
sql, already rendered by the first pass, was compiled a second time with its own rendered output as the template source. This restricts the second pass to("slack_message",).Why it matters
Re-rendering turns a field's output back into template source, so Jinja syntax that arrived inside a context value is evaluated on the second pass rather than staying literal.
A DAG doing the ordinary thing:
A user triggering that Dag supplies
conf = {"tenant": "{{ ... }}"}. After pass 1 the operator'ssqlholds that Jinja verbatim; pass 2 then evaluates it against the task context, which includes thevarandconnaccessors.The practical impact today is limited — the re-rendered
sqlis not sent to Slack (onlyslack_messageis), and the query has already run by then, so the evaluated result lands in the rendered-template record rather than anywhere it can act. I am not reporting this as a vulnerability, and I checked the exfiltration paths before opening this rather than assuming them. But "a field's rendered output is re-compiled as a template" is not a property this operator should have, and the fix is smaller than reasoning about where the output ends up.The first pass is unaffected, and
slack_messagestill renders exactly as before — it is simply the only field the second pass touches now.Test
test_second_render_leaves_already_rendered_fields_alonerenders with a context wheredsresolves to a value containing Jinja, and assertssqlstill holds it literally afterexecute().Against the current code the second pass evaluates it and the test fails:
With this change the full file passes — 11 tests, including the three existing render tests, which are unaffected.
Same shape elsewhere
Other operators defer part of their rendering the same way and may re-render more than the deferred field. I have not audited them and this PR deliberately does not touch them; flagging in case a maintainer wants them looked at:
@task.bashcommon.aiandcommon.sqldecoratorskubernetes_cmdgeneric_transferNotes
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5) following the guidelines
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.