You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "potiuk (via GitHub)" <gi...@apache.org> on 2026/08/01 20:47:03 UTC

[PR] Claim callbacks through the Execution API before running them [airflow]

potiuk opened a new pull request, #70927:
URL: https://github.com/apache/airflow/pull/70927

   A CeleryExecutor worker runs an `ExecuteCallback` workload by importing the
   callback path and calling it in a supervised subprocess. Unlike a task or a
   connection test, a callback made **no authenticated call to the Execution API
   before doing so** — so the workload token minted for it in `ExecuteCallback.make`
   was never redeemed anywhere, and the callback executed whether or not the token
   was valid. This closes that gap and gives callbacks the `RUNNING` state the
   workload state machine already describes (`QUEUED → RUNNING → SUCCESS/FAILED`).
   
   ### Change
   
   - New `POST /execution/callbacks/{callback_id}/run`, scoped `token:workload` +
     `cb:self`, that atomically transitions the callback `QUEUED → RUNNING`.
   - `supervise_callback` calls it **before** starting the subprocess — mirroring
     `supervise_connection_test`, which calls the server before touching credentials.
   - `cb:self` enforcement mirrors the existing `ct:self` for connection tests.
   - Versioned as `2026-08-01`, so a client negotiating an older API version behaves
     as though the endpoint does not exist.
   
   The server validates the token (workload scope, subject == this callback id), so
   a missing or mismatched token is refused before the callback body runs. The
   transition is single-shot, so a redelivered/replayed message that reaches an
   already-claimed callback is refused rather than run twice.
   
   ### Tests
   
   - `test_callbacks.py` — claim transitions QUEUED→RUNNING; 404; 422; and 409 on a
     callback already RUNNING/terminal (replay protection)
   - `TestCbSelfScopeEnforcement` — a token minted for another callback is rejected (403)
   - `test_token_scope_boundaries` — the route is registered workload-only
   - `TestSuperviseCallbackClaimsBeforeExecuting` — the claim runs **before** the
     subprocess, and a rejected claim prevents it from starting. Verified: removing
     the claim call makes both fail.
   
   ---
   
   @vincbeck @o-nikolas — flagging you as the security-team folks most across this
   area. Please pull in @ferruzzi and anyone else who worked on the workload /
   callback execution path if I have missed them.
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes — Claude Opus 5 (1M context)
   
   Generated-by: Claude Opus 5 (1M context) following the guidelines at
   https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "seanghaeli (via GitHub)" <gi...@apache.org>.
seanghaeli commented on PR #70927:
URL: https://github.com/apache/airflow/pull/70927#issuecomment-5211143340

   Yes, I think this PR fully overlaps with https://github.com/apache/airflow/pull/71192. Since Ash has already taken a look at it, let's land that one? In the linked PR, the token is more narrowly scoped to just the `callback` instead of `workload`.
   
   A gap in mine though is that it doesn't include API versioning bundle, so I'll pull that over.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "potiuk (via GitHub)" <gi...@apache.org>.
potiuk commented on PR #70927:
URL: https://github.com/apache/airflow/pull/70927#issuecomment-5320185370

   Just rebased onto main — the conflicts were in the Execution API version registry and the generated Task SDK datamodels, both resolved (the generated file was re-generated rather than hand-edited, and comes out byte-identical).
   
   @amoghrajesh you review most of the Execution API and Task SDK work; would you take a look?
   
   One thing worth an opinion on while reviewing: this adds Execution API version `2026-08-01`, but main's head version has since moved to `2026-10-30`, so the new version now sits below the head rather than at it. I have left it as authored rather than renumbering it during the rebase.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "ferruzzi (via GitHub)" <gi...@apache.org>.
ferruzzi commented on PR #70927:
URL: https://github.com/apache/airflow/pull/70927#issuecomment-5208652228

   @seanghaeli  - weren't you working on this a while ago, or was that a different workload token? 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "ferruzzi (via GitHub)" <gi...@apache.org>.
ferruzzi commented on code in PR #70927:
URL: https://github.com/apache/airflow/pull/70927#discussion_r3731596195


##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/callbacks.py:
##########
@@ -0,0 +1,82 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from uuid import UUID
+
+from cadwyn import VersionedAPIRouter
+from fastapi import HTTPException, Security, status
+
+from airflow.api_fastapi.common.db.common import SessionDep
+from airflow.api_fastapi.execution_api.datamodels.callback import CallbackRunResponse
+from airflow.api_fastapi.execution_api.security import ExecutionAPIRoute, require_auth
+from airflow.models.callback import Callback
+from airflow.utils.state import CallbackState
+
+# ``cb:self`` makes the server enforce that the presented workload token was minted for *this*
+# callback id (see ``require_auth``). Combined with ``token:workload``, a worker cannot reach this
+# route without a validly-signed token whose subject is the callback it is claiming.
+router = VersionedAPIRouter(
+    route_class=ExecutionAPIRoute,
+    dependencies=[
+        Security(require_auth, scopes=["cb:self", "token:workload"]),
+    ],
+)
+
+
+@router.post(
+    "/{callback_id}/run",
+    responses={
+        status.HTTP_404_NOT_FOUND: {"description": "Callback not found"},
+        status.HTTP_409_CONFLICT: {"description": "Callback is not in a claimable state"},
+    },
+)
+def run_callback(
+    callback_id: UUID,
+    session: SessionDep,
+) -> CallbackRunResponse:
+    """
+    Atomically claim a callback for execution and transition it to RUNNING.
+
+    The worker calls this *before* importing and invoking the callback. Its purpose is twofold:
+    the ``Security`` dependency forces the API server to validate the worker's token before any
+    callback code runs, and the ``QUEUED -> RUNNING`` transition is single-shot, so a redelivered
+    or replayed message that reaches a callback already RUNNING or terminal is refused rather than
+    executed a second time.
+    """
+    callback = session.get(Callback, callback_id, with_for_update=True)
+    if callback is None:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail={"reason": "not_found", "message": f"Callback {callback_id} not found"},
+        )
+
+    if callback.state != CallbackState.QUEUED:
+        raise HTTPException(
+            status_code=status.HTTP_409_CONFLICT,
+            detail={
+                "reason": "conflict",
+                "message": (
+                    f"Callback {callback_id} is in state {callback.state}; it can only be claimed "
+                    "while QUEUED."
+                ),
+            },
+        )
+
+    callback.state = CallbackState.RUNNING

Review Comment:
   Doesn't this conflict with the executors which already change the state from QUEUED to RUNNING?
   
   `LocalExecutor` queues the RUNNING event before calling `run_workload()`, so it would be RUNNING before it gets here and this `if not QUEUED` block would throw an exception, failing the callback by default before it ever gets run.  The AWS executors all set the state to RUNNING in `self.running_state()` as well.  I'm pretty sure Celery is the exception here and every other executor would start failing.
   
   I suspect the executors need to be updated to not also change the state, or this `if queued` check needs to be expanded to `if not terminal state`, maybe?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "potiuk (via GitHub)" <gi...@apache.org>.
potiuk closed pull request #70927: Claim callbacks through the Execution API before running them
URL: https://github.com/apache/airflow/pull/70927


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "potiuk (via GitHub)" <gi...@apache.org>.
potiuk commented on PR #70927:
URL: https://github.com/apache/airflow/pull/70927#issuecomment-5318670072

   Agreed — these are the same change, and since you've already pulled the versioning bundle across, #71192 is the one to land. Your token model is the better of the two: mine only forces the workload token to be validated once, whereas yours makes it single-use and narrows what the callback can do after the exchange. Closing this one as superseded.
   
   Two things worth carrying over before it goes:
   
   1. The `supervise_callback` tests from here — `test_claim_runs_before_the_subprocess_starts` and `test_a_rejected_claim_prevents_the_subprocess_from_starting`. #71192 tests the endpoint and the client, but nothing at the supervisor level, and the ordering assertion is what actually pins the property we care about: no callback code runs until the token is redeemed.
   2. `callback:self` → `cb:self`, to match the existing `ti:self` / `ct:self` naming.
   
   Happy to port both over to your branch myself if that's easier — just say the word and I'll open a PR against it.
   
   One thing that came over from my branch and needs a fresh look on rebase: the bundle now has `Version("2026-08-01", AddCallbackRunEndpoint)` sitting below `Version("2026-10-30", ...)`. The date was fine when I opened this on Aug 1, but 2026-10-30 has since shipped without the endpoint, so as it stands a client negotiating 2026-10-30 gets a route introduced by an earlier-dated version. Worth re-dating past 2026-10-30.
   
   No need to port `TestCbSelfScopeEnforcement` — your real-JWT test in `versions/head/test_callbacks.py` covers that ground better.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Claim callbacks through the Execution API before running them [airflow]

Posted by "seanghaeli (via GitHub)" <gi...@apache.org>.
seanghaeli commented on PR #70927:
URL: https://github.com/apache/airflow/pull/70927#issuecomment-5271997210

   Added the versioning bundle code to https://github.com/apache/airflow/pull/71192


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org