From c9afccb1edd95e1f1f5a658b9fd7342ea0736d66 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Thu, 30 Jul 2026 16:35:51 +0200 Subject: [PATCH] fix(flows): mint fresh orchestration token so long steps don't expire the result-fetch JWT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A flow step's ephemeral JWT is minted at step pull time with a lifetime of SCRIPT_TOKEN_EXPIRY (900s on cloud) and reused to drive post-completion flow orchestration — including the next step's input-transform isolated-eval, which fetches prior steps' results (e.g. `[...results.x]`). If the step whose completion triggers that fetch ran longer than the token's lifetime minus the 60s JWT leeway (~16min on cloud), the reused token is already expired and the fetch is rejected as anonymous: Failed to fetch results for step 'x': Bad request: As a non logged in user, you can only see jobs ran by anonymous users This surfaces as an intermittent, hard-to-diagnose failure of long-running flows (per-step duration, not total flow duration). Mint a fresh token for flow-step completions so the orchestration client's lifetime is independent of how long the finished step ran (falls back to the step token on error). Co-Authored-By: Claude Opus 4.8 --- .../windmill-worker/src/result_processor.rs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index 6cf2a261ae..c0cdae4fff 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -689,8 +689,43 @@ pub async fn handle_receive_completed_job( killpill_rx: &tokio::sync::broadcast::Receiver<()>, #[cfg(feature = "benchmark")] bench: &mut BenchmarkIter, ) -> Option> { - let token = jc.token.clone(); + // WIN-2263: a flow step's ephemeral JWT (`jc.token`) is minted at step pull time with a + // lifetime of `SCRIPT_TOKEN_EXPIRY` (900s on cloud). It is reused here to build the client + // that drives post-completion flow orchestration — including the next step's input-transform + // isolated-eval, which fetches prior steps' results (`[...results.x]`). When the finished step + // ran longer than the token's lifetime (minus the 60s JWT leeway), that reused token is already + // expired and the result fetch is rejected as anonymous. Mint a fresh token for flow-step + // completions so the orchestration client's lifetime is independent of how long the step ran. let workspace = jc.job.workspace_id.clone(); + let token = if jc.job.is_flow_step() { + match windmill_common::auth::create_token_for_owner( + db, + &jc.job.workspace_id, + &jc.job.permissioned_as, + "ephemeral-script", + *windmill_common::worker::SCRIPT_TOKEN_EXPIRY, + &jc.job.permissioned_as_email, + &jc.job.id, + None, + Some(format!( + "job-span-{}", + jc.job.flow_innermost_root_job.unwrap_or(jc.job.id) + )), + ) + .await + { + Ok(t) => t, + Err(e) => { + tracing::warn!( + "WIN-2263: could not mint fresh flow-orchestration token for job {}, reusing step token: {e:#}", + jc.job.id + ); + jc.token.clone() + } + } + } else { + jc.token.clone() + }; let client = AuthedClient::new(base_internal_url.to_string(), workspace, token, None); let job = jc.job.clone(); let mem_peak = jc.mem_peak.clone();