windmill-labs/windmill · error

job not found or parent flow not in queue: {}

Error message

job not found or parent flow not in queue: {}

What it means

Resuming a step requires its parent flow job to exist and be currently sitting in the queue (locked FOR UPDATE on the queue row). This error is thrown when the query finds no such job: the job id is wrong, the flow is not queued, or the parent flow row cannot be locked.

Source

Thrown at backend/windmill-api/src/jobs.rs:5370

            q.suspend AS "suspend!",
            j.runnable_path AS script_path,
            j.permissioned_as_email AS email,
            (ji.kind IN ('flow', 'flowpreview', 'singlestepflow')) AS "is_flow_level!",
            (ji.kind NOT IN ('flow', 'flowpreview', 'singlestepflow') AND q.id = ji.id) AS "is_wac!"
        FROM job_info ji
        JOIN v2_job_queue q ON q.id = CASE
            WHEN ji.kind IN ('flow', 'flowpreview', 'singlestepflow') THEN ji.id
            ELSE COALESCE(ji.parent_job, ji.id)
        END
        JOIN v2_job j ON j.id = q.id
        LEFT JOIN v2_job_status s ON s.id = q.id
        FOR UPDATE OF q
        "#,
        job_id,
    )
    .fetch_optional(db)
    .await?
    .ok_or_else(|| anyhow::anyhow!("job not found or parent flow not in queue: {}", job_id))?;

    let flow_info = FlowInfo {
        id: result.id,
        flow_status: result.flow_status,
        suspend: result.suspend,
        script_path: result.script_path,
        email: Some(result.email),
    };

    Ok((flow_info, result.is_flow_level, result.is_wac))
}

async fn get_suspended_flow_info<'c>(
    job_id: Uuid,
    tx: &mut Transaction<'c, Postgres>,
) -> error::Result<(FlowInfo, Uuid, bool)> {
    let flow = sqlx::query_as!(
            FlowInfo,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Verify the flow run is still suspended/queued in the runs UI before resuming
  2. Use the correct job identifier for the suspended step (not the completed run's id)
  3. Re-run the flow if it already completed or was canceled — resumption is impossible
  4. Check that the parent flow job was not purged by retention/cleanup policies

Example fix

// before
resumeFlow(wId, completedRunId, resumeId) // throws
// after
const run = await getRun(wId, flowId);
if (run.state === 'suspended') resumeFlow(wId, flowId, resumeId);
Defensive patterns

Strategy: validation

Validate before calling

// only attempt resume when the flow run is still waiting
const run = await client.getJobRun(wId, flowJobId);
if (run.state !== 'suspended' && run.state !== 'waiting') {
  throw new Error('flow not resumable (state: ' + run.state + ')');
}

Try / catch

try {
  await client.resumeStep(wId, flowJobId, resumeId, payload);
} catch (e) {
  if (/job not found or parent flow not in queue/.test(e.message)) {
    // run already finished/canceled: surface as a no-op, don't retry
  } else throw e;
}

Prevention

When it happens

Trigger: POSTing a resume for a step whose parent flow already finished; resuming a job id that belongs to a script, not a suspended flow step; the parent flow was canceled or purged before the resume arrived.

Common situations: Calling the resume API with the step job id instead of an id resolvable through a queued parent flow; racing a canceled flow; stale webhook URLs after the run completed.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/d35e755054e3cc85. Report an issue: GitHub.