---
title: Durable hook resume
description: resumeHook() durably writes hook_received and only then publishes the workflow wake, so a resolved call can never be lost to a disposal race.
---

# Durable hook resume



# Durable hook resume

## Motivation

The previous lazy path published the serialized hook payload on the workflow
queue and left the queue consumer to create `hook_received`. If the hook was
disposed after `resumeHook()` returned but before the consumer write committed,
that write was rejected and the acknowledged queue delivery could not resume the
workflow: the caller was told the resume succeeded, and it was lost.

`resumeHook()` now resolves only after both the durable event write and the
workflow wake have succeeded, in that order.

## Design

The dispatch is strictly serial:

1. The hook is resolved by token. An unknown token throws `HookNotFoundError`.
2. The producer writes `hook_received` durably into the run's event log. A
   client-minted `resumeId` and payload digest ride the write when the backend
   supports atomic resume claims, so transport-level retries of the same write
   converge on exactly one committed event. A write refused because the hook
   was disposed or the run ended throws `HookNotFoundError`.
3. Only after the write is acknowledged does the producer publish the workflow
   wake. The wake carries no payload — the payload lives in the event log — so
   nothing rides on the queue message but the trigger. Publication is retried
   a bounded number of times.

Because the event is committed before the wake exists, a disposal or run
completion racing the queue delivery cannot erase a resume the caller was told
succeeded: the delivery replays the committed event from the log.

* `ResumedHook.resilientResume` remains on the type for source compatibility
  and is no longer set. The internal `resumeHookDurable()` entry point is
  removed; `resumeHook()` itself now provides the durable guarantee.

A resolved call proves that the event is durable and the wake was accepted.
`HookNotFoundError` proves this invocation committed no event. Any other thrown
error is ambiguous only in *dispatch*, never in durability: a wake failure
after the write leaves the event committed, and any later wake of the run
(from any source) delivers it. A fresh `resumeHook()` invocation mints a new
`resumeId`, so blindly retrying a failed call can append a second
`hook_received`; callers that need at-most-once behavior across separate
invocations must deduplicate on their own request key.

## Behavior change: resumes against an ended run

The lazy path never observed the server's rejection — it published a message
and resolved, so a resume against a run that had already ended reported
success (reachable whenever the hook record outlives its run, e.g. token
retention). The durable write restores the check: **a resume against an ended
run now throws `HookNotFoundError`**, and a late webhook delivery to a
finished run answers 404 where it previously answered 202. Senders that treat
4xx as terminal will stop retrying such deliveries; that is the correct
signal, since nothing can resume an ended run.

A transient write conflict (HTTP 409, e.g. an event-slot conflict that
escaped the server's internal retry budget under contention) is no longer
re-keyed to `HookNotFoundError`. It surfaces as a retryable error, and its
rejected transaction committed nothing, so retrying the resume is safe.

## Compatibility

Nothing about the queue message changes: the wake has the same shape the
sequential path always published, so no consumer, backend, or server
coordination is needed and either side can roll back independently.

Consumers continue to accept legacy `hookInput` messages from older producers,
materializing their payload before replay. This permits rolling upgrades
without a coordinated producer and consumer deployment.

`WORKFLOW_DISABLE_LAZY_HOOK_RESUME` no longer gates anything and is ignored:
there is no lazy path left to disable.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)