Skip to content
Get started
Get started
Flow Failure Handling: When a Child Fails
View Markdown
guide · flow producer

One child dies. Now what?

The default is to keep waiting, which is rarely what you want. These four options let a failed child fail the parent, be ignored, cancel its siblings, or let the parent run on whatever succeeded.

By default a parent just keeps waiting for its remaining children. Four child options change what happens when a child fails terminally: its last attempt fails, it throws UnrecoverableError, or stall detection, lock expiry or startup recovery after a crash uses up its attempts. The policy applies the same way on every path:

OptionBehavior
failParentOnFailureParent immediately moves to failed (into the DLQ, error Child job <id> failed: <error>), even if other children are still running. Siblings are not cancelled and the parent’s own attempts are not used
removeDependencyOnFailureThe failed child is silently dropped from the parent’s dependencies; the parent proceeds as if it never existed
ignoreDependencyOnFailureLike the above, but the failure is recorded; the parent can read it via job.getIgnoredChildrenFailures()
continueParentOnFailureParent is promoted to run immediately; job.getFailedChildrenValues() lists the failures recorded before it started, and it can cancel leftover children

Choose at most one policy per child. A parent that failParentOnFailure moves to failed has failed terminally too, so its own policy then applies to its parent: failParentOnFailure on every level fails the whole branch up to the root. This holds in every mode (in-memory, SQLite and PostgreSQL).

Policies are accepted only on a job that has a parent: flow.add() / addBulk() children and addBulkThen parallel jobs. A root node, an addChain step or an addTree child carrying one is rejected with a flow failure policy requires a parent; in a chain, a step that fails terminally leaves the steps after it waiting.

With a persistent backend (SQLite or PostgreSQL), the selected policy and any unresolved failure record are durable: a broker restart cannot reset the child to default behavior or put an already-released parent back into waiting-children.

A worked example with continueParentOnFailure, useful when the parent should decide how to handle partial failure:

import { FlowProducer, Worker } from 'bunqueue/client';
const flow = new FlowProducer({ embedded: true });
await flow.add({
name: 'pipeline',
queueName: 'main',
data: {},
children: [
{ name: 'step-a', queueName: 'workers', data: {}, opts: { continueParentOnFailure: true } },
{ name: 'step-b', queueName: 'workers', data: {}, opts: { continueParentOnFailure: true } },
{ name: 'step-c', queueName: 'workers', data: {} },
],
});
const worker = new Worker('main', async (job) => {
const failed = await job.getFailedChildrenValues();
// { 'workers:job-abc': 'step-a failed', ... }
if (Object.keys(failed).length > 0) {
await job.removeUnprocessedChildren(); // cancel direct children still queued
return { status: 'partial', failedSteps: failed };
}
return { status: 'complete' };
}, { embedded: true });

And with ignoreDependencyOnFailure, when the parent should continue with partial data:

import { Worker } from 'bunqueue/client';
const worker = new Worker('reports', async (job) => {
const ignored = await job.getIgnoredChildrenFailures();
// { 'workers:job-abc': 'enrichment API timeout' }
return { partial: Object.keys(ignored).length > 0 };
}, { embedded: true });

All four failure-propagation options are ordinary job options and reach the wire in every SDK. The reading helpers (getFailedChildrenValues, getIgnoredChildrenFailures, removeUnprocessedChildren) are job methods in the Bun package and the TypeScript SDK, and Queue methods taking the job id in the Python SDK.

Flow ProducerYour first parent/child graph, and what is guaranteed
Flow PatternsChains, fan-in, trees, reading child results, options
Flow Producer ReferenceEvery producer method, job helper and step field