Deliver what arrived on timeout
With partialOnTimeout, a batch that misses its deadline wakes with the items filled so far instead of false, so the workflow can act on them.
The situation
Twelve inspections are due by end of shift. If ten arrive, the workflow should ship those ten and reschedule two, not treat the whole order as failed.
The recipe
const result = await conditional<Record<string, InspectionV1> & { $trigger?: 'timeout' }>(signalId, { role: 'inspection', metadata: { orderId }, batch: partKeys, timeout: '8h', partialOnTimeout: true,});if (result === null) return cancelled(orderId);if (result && result.$trigger === 'timeout') { const done = partKeys.filter((k) => k in result); return reschedule(orderId, partKeys.filter((k) => !done.includes(k)));}Without the flag, a batch timeout returns false and the partial items stay on the expired row for audit. With it, the timer resumes the wait with the filled items plus $trigger: 'timeout'.
The guarantee
The value the workflow receives is the value the expired row stores as resolver_payload. The flag is stored on the row as envelope.batch_partial_on_timeout at creation, so the behavior is fixed for the life of the wait. Existing batch waits are unchanged. See Batches and accumulators.
Related patterns
The escalation boundary
Batch: N contributions, one wake
One row collects a named set of submissions and wakes the workflow once, when the last one lands, with the whole collection.
The escalation boundary
A timeout is a delivery window
On a container, the timer closes the window and delivers what it holds. The type has no false branch to handle.
The escalation boundary
Answer, timeout or cancel
Every wait ends in one of three typed values: the answer, false on timeout, or null on cancel. Each branch is explicit in code.
The escalation boundary
Wake on flag
A contributor flags one pending item and the workflow wakes at once with what has arrived and the flag, so it can reroute the order.
