windmill-labs/windmill · error

parent flow job not found

Error message

parent flow job not found

What it means

When resolving a step's parent flow inside a transaction, the flow job row (WHERE j.id = $1) is expected to exist; if fetch_optional returns None the lookup fails with this error. It indicates the parent flow job row is missing from the jobs table at resume time.

Source

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

    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,
            r#"
            SELECT j.id AS "id!", COALESCE(s.flow_status, s.workflow_as_code_status) as flow_status, q.suspend AS "suspend!", j.runnable_path as script_path, j.permissioned_as_email as email
            FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_status s USING (id)
            WHERE j.id = $1
            "#,
            job_id,
        )
        .fetch_optional(&mut **tx)
        .await?
        .ok_or_else(|| anyhow::anyhow!("parent flow job not found"))?;

    // Try to extract step job_id from FlowStatus modules (classic flow path)
    let step_job_id = flow
        .flow_status
        .as_ref()
        .and_then(|v| serde_json::from_value::<FlowStatus>(v.clone()).ok())
        .and_then(|s| match s.modules.get(s.step as usize) {
            Some(FlowStatusModule::WaitingForEvents { job, .. }) => Some(job.to_owned()),
            _ => None,
        });

    if let Some(step_job_id) = step_job_id {
        // Classic flow
        Ok((flow, step_job_id, false))
    } else if flow.suspend > 0 {
        // WAC approval: no FlowStatus modules, but the job is suspended
        // The flow_status here comes from COALESCE(flow_status, workflow_as_code_status),
        // so for WAC it may contain approval_conditions from flow_status column

View on GitHub (pinned to e474e8803c)

Solutions

  1. Confirm the parent flow job id exists (query the jobs table or runs UI)
  2. Use the current run's job id from the flow run details, not an old one
  3. Re-run the flow; the original run's data is gone
  4. Check retention/cleanup settings if runs disappear too quickly

Example fix

// before
await api.resume(wId, staleJobId, payload);
// after
const run = await api.getJob(wId, jobId); // 404 here before attempting resume
if (run) await api.resume(wId, jobId, payload);
Defensive patterns

Strategy: validation

Validate before calling

// verify the parent flow exists before resuming
const flow = await client.getJobRun(wId, parentFlowJobId).catch(() => null);
if (!flow) throw new Error('parent flow gone: re-run the flow');

Type guard

function flowExists(run) {
  return run !== null && run !== undefined && typeof run.id === 'string';
}

Prevention

When it happens

Trigger: Calling resume on a job id whose parent flow row no longer exists (deleted/purged); passing a malformed or non-existent job id; a race where the flow was removed between queue lookup and the transactional fetch.

Common situations: Stale approval links kept for days while cleanup removed the run; typos in job ids in scripts calling the resume API; retention jobs deleting finished runs referenced by old links.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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