hook-conflict
This error occurs when you try to create a hook with a token that is already in use by another active workflow run. Hook tokens must be unique across all running workflows in your project.
Error message
Why this happens
Hooks use tokens to identify incoming webhook payloads. When you create a hook with createHook({ token: "my-token" }), the Workflow runtime reserves that token for your workflow run. If another workflow run is already using that token, a conflict occurs.
This typically happens when:
- Two workflows start simultaneously with the same hardcoded token
- A previous workflow run is still waiting for a hook when a new run tries to use the same token
Common causes
Hardcoded token values
Solution: Use unique tokens that include the run ID or other unique identifiers.
Omitting the token (auto-generated)
The safest approach is to let the Workflow runtime generate a unique token automatically:
Handling hook conflicts
When a hook conflict occurs, awaiting the hook will throw a HookConflictError. The error exposes the token that conflicted and, for current worlds, the run ID that currently owns it. conflictingRunId remains optional for compatibility with older persisted events and world implementations, so guard it before delegating:
This pattern is useful when you want to detect duplicate processing inside the workflow. Runtime APIs such as resumeHook() and getRun() must be called outside workflow functions, for example from an API route or in a step.
Delegate to the active Run
In idempotency flows, a conflict means another active run already owns the hook token. You can return the duplicate-processing payload from the workflow, resume the active hook to deliver the payload to the existing run, then use getRun(result.runId) to wait for, stream, or cancel the active run:
If the caller needs live output instead of the final result, return activeRun.getReadable() from the same branch. If the duplicate request should replace the active work, call await activeRun.cancel() after inspecting the run.
Take the token over
If the newest run should always own the token, create the hook with experimental_force: true. The new run takes the token instead of getting HookConflictError, and the previous owner's await hook rejects with HookForceClaimedError.
This enables zero-downtime transfers for hooks so one run can hand off a hook to another without dropping messages.
A run started at a Workflow spec version below 8, including runs started by older SDK releases, can't be taken from, so the forced hook still gets HookConflictError in that case.
When hook tokens are released
Hook tokens are automatically released when:
- The workflow run completes (successfully or with an error)
- The workflow run is canceled
- The hook is explicitly disposed
After a workflow completes, its hook tokens become available for reuse by other workflows.
Best practices
- Use auto-generated tokens when possible - they are guaranteed to be unique
- Include unique identifiers if you need custom tokens (order ID, user ID, etc.)
- Avoid reusing the same token across multiple concurrent workflow runs
- Consider using webhooks (
createWebhook) if you need a fixed, predictable URL that can receive multiple payloads - Use
experimental_forcewhen a newer run should replace the run holding the token
Related
- Hooks - Learn more about using hooks in workflows
- getRun - Retrieve or control the active run
- resumeHook - Deliver data to the active hook
- createWebhook - Alternative for fixed webhook URLs