UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

178 lines (133 loc) • 6.4 kB
--- title: hook-conflict description: Hook tokens must be unique across all running workflows in your project. type: troubleshooting summary: Resolve hook token conflicts by using unique or auto-generated tokens. prerequisites: - /docs/foundations/hooks related: - /docs/api-reference/workflow/create-hook - /docs/api-reference/workflow/define-hook --- 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 ``` Hook token "<token>" is already in use by another workflow ``` ## 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: 1. **Two workflows start simultaneously** with the same hardcoded token 2. **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 {/* @skip-typecheck: incomplete code sample */} ```typescript lineNumbers // Error - multiple concurrent runs will conflict export async function processPayment() { "use workflow"; const hook = createHook({ token: "payment-hook" }); // [!code highlight] // If another run is already waiting on "payment-hook", this will fail const payment = await hook; } ``` **Solution:** Use unique tokens that include the run ID or other unique identifiers. ```typescript lineNumbers import { createHook } from "workflow"; export async function processPayment(orderId: string) { "use workflow"; // Include unique identifier in token const hook = createHook({ token: `payment-${orderId}` }); // [!code highlight] const payment = await hook; } ``` ### Omitting the Token (Auto-generated) The safest approach is to let the Workflow runtime generate a unique token automatically: ```typescript lineNumbers import { createHook } from "workflow"; export async function processPayment() { "use workflow"; const hook = createHook(); // Auto-generated unique token // [!code highlight] console.log(`Send webhook to token: ${hook.token}`); const payment = await hook; } ``` ## 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: ```typescript lineNumbers import { createHook } from "workflow"; import { HookConflictError } from "@workflow/errors"; export async function processPayment(orderId: string) { "use workflow"; const hook = createHook({ token: `payment-${orderId}` }); try { const payment = await hook; // [!code highlight] return { success: true, payment }; } catch (error) { if (HookConflictError.is(error)) { // [!code highlight] // Another workflow is already processing this order console.log(`Conflicting token: ${error.token}`); if (error.conflictingRunId) { console.log(`Active run: ${error.conflictingRunId}`); } return { success: false, reason: "duplicate-processing", token: error.token, runId: error.conflictingRunId }; } throw error; // Re-throw other errors } } ``` 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: ```typescript lineNumbers import { getRun, resumeHook, start } from "workflow/api"; import { processPayment } from "@/workflows/process-payment"; type ProcessPaymentResult = | { success: true; payment: unknown } | { success: false; reason: "duplicate-processing"; token: string; runId?: string; }; export async function POST(request: Request) { const { orderId, payment } = await request.json(); const run = await start(processPayment, [orderId]); const result = (await run.returnValue) as ProcessPaymentResult; if ( result.success === false && result.reason === "duplicate-processing" && result.runId ) { await resumeHook(result.token, payment); // [!code highlight] const activeRun = getRun(result.runId); // [!code highlight] return Response.json({ delegatedToRunId: activeRun.runId, result: await activeRun.returnValue }); } return Response.json(result); } ``` 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. ## When Hook Tokens Are Released Hook tokens are automatically released when: - The workflow run **completes** (successfully or with an error) - The workflow run is **cancelled** - The hook is explicitly **disposed** After a workflow completes, its hook tokens become available for reuse by other workflows. ## Best Practices 1. **Use auto-generated tokens** when possible - they are guaranteed to be unique 2. **Include unique identifiers** if you need custom tokens (order ID, user ID, etc.) 3. **Avoid reusing the same token** across multiple concurrent workflow runs 4. **Consider using webhooks** (`createWebhook`) if you need a fixed, predictable URL that can receive multiple payloads ## Related - [Hooks](/docs/foundations/hooks) - Learn more about using hooks in workflows - [getRun](/docs/api-reference/workflow-api/get-run) - Retrieve or control the active run - [resumeHook](/docs/api-reference/workflow-api/resume-hook) - Deliver data to the active hook - [createWebhook](/docs/api-reference/workflow/create-webhook) - Alternative for fixed webhook URLs