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 columnView on GitHub (pinned to e474e8803c)
Solutions
- Confirm the parent flow job id exists (query the jobs table or runs UI)
- Use the current run's job id from the flow run details, not an old one
- Re-run the flow; the original run's data is gone
- 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
- Keep retention windows longer than your approval SLA
- Fetch the current run id from the UI/API instead of caching it
- Watch for cleanup jobs deleting runs with pending approvals
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
- job not found or parent flow not in queue: {}
- Flow ${flowPath} not found
- Database not found
- the flow is not in a suspended state anymore
- unable to find the flow status in the flow job
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/34727ab4730435ac.
Report an issue: GitHub.