windmill-labs/windmill · error
job {} has no parent flow
Error message
job {} has no parent flow What it means
The jobs API looked up a job that is expected to be part of a flow run, but the stored job row has no parent_job (parent flow) reference. The endpoint needs the enclosing flow's job ID (e.g. to resolve flow-level context or act on the flow) and throws when the job is standalone. This typically means the caller passed a job ID that is not a flow child — it is a top-level script/flow run.
Source
Thrown at backend/windmill-api/src/jobs.rs:6082
SELECT kind::text as "kind!", parent_job
FROM v2_job
WHERE id = $1
"#,
job_id
)
.fetch_optional(db)
.await?
.ok_or_else(|| anyhow::anyhow!("job not found: {}", job_id))?;
// If it's a flow job, return the job_id itself
if job_info.kind == "flow" || job_info.kind == "flowpreview" {
return Ok(job_id);
}
// Otherwise, return the parent flow ID
job_info
.parent_job
.ok_or_else(|| anyhow::anyhow!("job {} has no parent flow", job_id).into())
}
#[derive(Deserialize)]
struct CancelJob {
reason: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum PreviewKind {
Code,
Identity,
Noop,
Bundle,
Tarbundle,
ScriptHash,
}
View on GitHub (pinned to e474e8803c)
Solutions
- Verify the job_id you pass belongs to a child step inside a flow run (check job row's parent_job in the database: SELECT parent_job FROM job WHERE id = <job_id>).
- If you have the flow run's ID, use it directly instead of trying to resolve a parent from a child.
- For standalone script jobs, use the job-specific endpoints rather than the flow-scoped one.
- If the job should have a parent but does not, check whether it was created via the raw job-creation path instead of the flow executor.
Example fix
// before
let parent = get_parent_flow_id(standalone_job_id).await?; // Err: job X has no parent flow
// after
let job = get_job(job_id).await?;
let parent = match job.parent_job {
Some(pid) => pid,
None => job.id, // it's a root/standalone job; act on it directly
}; Defensive patterns
Strategy: type-guard
Validate before calling
const job = await client.getJob(jobId);
if (!job.parent_job) throw new Error(`job ${jobId} is standalone; use flow-specific APIs only with child jobs`); Type guard
function hasParentFlow(job) { return typeof job.parent_job === 'string' && job.parent_job.length > 0; } Try / catch
try {
const flowId = await resolveParentFlow(jobId);
} catch (e) {
if (String(e).includes('has no parent flow')) {
// treat job as root: operate on jobId itself
} else throw e;
} Prevention
- Only pass child-step job IDs to flow-scoped endpoints
- Check parent_job before resolving
- Keep the job ID of the flow run alongside child IDs in your tooling
When it happens
Trigger: Calling jobs.rs:6082's helper (resolving a job's parent flow ID) with a job_id whose `job_info.parent_job` is NULL — i.e. a standalone script run, a flow run itself (root), or a job created outside a flow context.
Common situations: Passing the flow run's own job ID instead of a child step's job ID; passing a standalone script job ID to an endpoint that only accepts flow-child jobs; job rows created by older/legacy code paths that never set parent_job; cancelled or cleaned-up jobs whose parent linkage was removed.
Related errors
- Not supported
- Job ${jobId} was not successful: ${JSON.stringify(error)}
- not a flow
- Cannot push flow ${remotePath}: missing inline script file(s
- Cannot push flow ${remotePath}: step(s) reference non-worksp
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/d229748afe50188e.
Report an issue: GitHub.