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

  1. 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>).
  2. If you have the flow run's ID, use it directly instead of trying to resolve a parent from a child.
  3. For standalone script jobs, use the job-specific endpoints rather than the flow-scoped one.
  4. 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

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


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