Skip to content

Answer, timeout or cancel

The escalation boundaryDashboardHTTPSDKCLIMCPconditional

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 situation

A person might answer, might not answer before the deadline, or someone might withdraw the request. A workflow that treats all three the same either acts on missing data or stalls forever. The return type should force the decision.

The recipe

workflows/approve-order.ts
import { Durable } from '@hotmeshio/hotmesh';
import { conditional, type LTEnvelope } from '@hotmeshio/long-tail';
export async function approveOrder(envelope: LTEnvelope) {
const { orderId } = envelope.data;
const { workflowId } = Durable.workflow.workflowInfo();
const decision = await conditional<{ approved: boolean }>(`approve-${workflowId}`, {
role: 'order-reviewer',
description: `Approve order ${orderId}`,
metadata: { orderId },
timeout: '72h',
});
if (decision === false) return { type: 'return' as const, data: { orderId, outcome: 'expired' } };
if (decision === null) return { type: 'return' as const, data: { orderId, outcome: 'cancelled' } };
return { type: 'return' as const, data: { orderId, outcome: decision.approved ? 'approved' : 'rejected' } };
}

conditional returns T | false | null. T is the resolver’s payload. false means the timeout timer fired first. null means the row was cancelled, explicitly or because the workflow was terminated.

The guarantee

The race between a resolve and the timer settles once. If the timer wins, the row moves to expired in a guarded update and a late resolve answers already-expired. If the resolve wins, the timer is inert. Omit timeout for an open-ended wait. See Conditional.