- Docs
- Cron, Retries & Flows
- Flow Producer
- Child Failures
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.
When a Child Fails
Section titled “When a Child Fails”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:
| Option | Behavior |
|---|---|
failParentOnFailure | Parent 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 |
removeDependencyOnFailure | The failed child is silently dropped from the parent’s dependencies; the parent proceeds as if it never existed |
ignoreDependencyOnFailure | Like the above, but the failure is recorded; the parent can read it via job.getIgnoredChildrenFailures() |
continueParentOnFailure | Parent 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 });import { FlowProducer, Worker } from 'bunqueue-client';
const flow = new FlowProducer();
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' };});from bunqueue import FlowProducer, Queue, Worker
flow = FlowProducer()queue = Queue("main")
flow.add({ "name": "pipeline", "queueName": "main", "data": {}, "children": [ {"name": "step-a", "queueName": "workers", "data": {}, "opts": {"continue_parent_on_failure": True}}, {"name": "step-b", "queueName": "workers", "data": {}, "opts": {"continue_parent_on_failure": True}}, {"name": "step-c", "queueName": "workers", "data": {}}, ],})
def process(job): failed = queue.get_failed_children_values(job.id) # {"workers:job-abc": "step-a failed", ...}
if failed: queue.remove_unprocessed_children(job.id) # cancel direct children still queued return {"status": "partial", "failed_steps": failed} return {"status": "complete"}
Worker("main", process).run()The child-failure queries (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. The failure options themselves work from PHP like anywhere else; read child outcomes by looking the child IDs up with getJob($childId) / getResult($childId) on the queue.
The child-failure queries (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. The failure options themselves work from Go like anywhere else; read child outcomes by looking the child IDs up with GetJob(childID) / GetResult(childID) on the queue.
The child-failure queries (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. The failure options themselves work from Rust like anywhere else; read child outcomes by looking the child IDs up with get_job(child_id) / get_result(child_id) on the queue.
The child-failure queries (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. The failure options themselves work from Elixir like anywhere else; read child outcomes by looking the child IDs up with get_job(child_id) / get_result(child_id) on the queue.
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 });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 };});queue = Queue("reports")
def process(job): ignored = queue.get_ignored_children_failures(job.id) # {"workers:job-abc": "enrichment API timeout"} return {"partial": bool(ignored)}
Worker("reports", process).run()The child-failure queries (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. The failure options themselves work from PHP like anywhere else; read child outcomes by looking the child IDs up with getJob($childId) / getResult($childId) on the queue.
The child-failure queries (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. The failure options themselves work from Go like anywhere else; read child outcomes by looking the child IDs up with GetJob(childID) / GetResult(childID) on the queue.
The child-failure queries (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. The failure options themselves work from Rust like anywhere else; read child outcomes by looking the child IDs up with get_job(child_id) / get_result(child_id) on the queue.
The child-failure queries (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. The failure options themselves work from Elixir like anywhere else; read child outcomes by looking the child IDs up with get_job(child_id) / get_result(child_id) on the queue.
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.
Where to go next
Section titled “Where to go next”| Flow Producer | Your first parent/child graph, and what is guaranteed |
| Flow Patterns | Chains, fan-in, trees, reading child results, options |
| Flow Producer Reference | Every producer method, job helper and step field |