workflow 5.0.0-beta.43 → 5.0.0-beta.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/internal/builtins.d.ts +4 -4
- package/dist/internal/builtins.js +6 -6
- package/docs/ai/chat-session-modeling.mdx +23 -24
- package/docs/ai/defining-tools.mdx +5 -6
- package/docs/ai/human-in-the-loop.mdx +11 -11
- package/docs/ai/index.mdx +20 -20
- package/docs/ai/message-queueing.mdx +6 -6
- package/docs/ai/meta.json +1 -0
- package/docs/ai/resumable-streams.mdx +28 -28
- package/docs/ai/sleep-and-delays.mdx +9 -9
- package/docs/ai/streaming-updates-from-tools.mdx +4 -4
- package/docs/api-reference/vitest/index.mdx +8 -8
- package/docs/api-reference/workflow/create-hook.mdx +15 -15
- package/docs/api-reference/workflow/create-webhook.mdx +15 -15
- package/docs/api-reference/workflow/define-hook.mdx +10 -10
- package/docs/api-reference/workflow/fatal-error.mdx +2 -2
- package/docs/api-reference/workflow/fetch.mdx +7 -7
- package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/workflow/get-writable.mdx +7 -7
- package/docs/api-reference/workflow/index.mdx +1 -1
- package/docs/api-reference/workflow/retryable-error.mdx +1 -1
- package/docs/api-reference/workflow/set-attributes.mdx +2 -2
- package/docs/api-reference/workflow/sleep.mdx +3 -3
- package/docs/api-reference/workflow-ai/durable-agent.mdx +42 -42
- package/docs/api-reference/workflow-ai/index.mdx +3 -3
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +28 -28
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +11 -11
- package/docs/api-reference/workflow-api/get-run.mdx +10 -10
- package/docs/api-reference/workflow-api/index.mdx +2 -4
- package/docs/api-reference/workflow-api/resume-hook.mdx +14 -14
- package/docs/api-reference/workflow-api/resume-webhook.mdx +3 -3
- package/docs/api-reference/workflow-api/start.mdx +16 -15
- package/docs/api-reference/workflow-astro/workflow.mdx +3 -3
- package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
- package/docs/api-reference/workflow-errors/index.mdx +6 -6
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +9 -9
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +6 -6
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
- package/docs/api-reference/workflow-globals.mdx +12 -11
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +2 -2
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +4 -4
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +1 -1
- package/docs/api-reference/workflow-nest/workflow-module.mdx +4 -4
- package/docs/api-reference/workflow-next/with-workflow.mdx +16 -16
- package/docs/api-reference/workflow-nitro/index.mdx +6 -6
- package/docs/api-reference/workflow-nuxt/index.mdx +4 -4
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +5 -5
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +4 -4
- package/docs/api-reference/workflow-observability/index.mdx +6 -8
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +2 -2
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +3 -3
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +4 -4
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +4 -4
- package/docs/api-reference/workflow-runtime/create-world.mdx +8 -8
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +6 -6
- package/docs/api-reference/workflow-runtime/get-world.mdx +4 -4
- package/docs/api-reference/workflow-runtime/health-check.mdx +1 -1
- package/docs/api-reference/workflow-runtime/index.mdx +2 -4
- package/docs/api-reference/workflow-runtime/set-world.mdx +15 -13
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +6 -5
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +9 -9
- package/docs/api-reference/workflow-runtime/world/index.mdx +5 -3
- package/docs/api-reference/workflow-runtime/world/queue.mdx +11 -11
- package/docs/api-reference/workflow-runtime/world/storage.mdx +62 -28
- package/docs/api-reference/workflow-runtime/world/streams.mdx +7 -7
- package/docs/api-reference/workflow-serde/index.mdx +1 -1
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +2 -2
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +7 -7
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +3 -3
- package/docs/api-reference/workflow-vite/workflow.mdx +5 -5
- package/docs/changelog/attributes-mvp.mdx +39 -39
- package/docs/changelog/batched-event-writes.mdx +12 -12
- package/docs/changelog/eager-processing.mdx +63 -63
- package/docs/changelog/index.mdx +3 -3
- package/docs/changelog/lazy-event-creation.md +27 -27
- package/docs/changelog/resilient-resume.mdx +5 -5
- package/docs/changelog/resilient-start.mdx +14 -14
- package/docs/changelog/step-message-ownership.mdx +47 -47
- package/docs/changelog/turbo-mode.md +20 -20
- package/docs/comparisons/index.mdx +13 -13
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +15 -15
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +12 -12
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +11 -11
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +19 -19
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +23 -23
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +18 -17
- package/docs/configuration/build-and-diagnostics.mdx +5 -5
- package/docs/configuration/cli-and-web-ui.mdx +4 -4
- package/docs/configuration/runtime-tuning.mdx +86 -23
- package/docs/configuration/worlds.mdx +28 -14
- package/docs/cookbook/advanced/child-workflows.mdx +25 -25
- package/docs/cookbook/advanced/publishing-libraries.mdx +40 -40
- package/docs/cookbook/advanced/serializable-steps.mdx +21 -21
- package/docs/cookbook/advanced/upgrading-workflows.mdx +31 -31
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +20 -20
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +22 -22
- package/docs/cookbook/common-patterns/batching.mdx +14 -14
- package/docs/cookbook/common-patterns/idempotency.mdx +9 -9
- package/docs/cookbook/common-patterns/rate-limiting.mdx +3 -3
- package/docs/cookbook/common-patterns/saga.mdx +19 -19
- package/docs/cookbook/common-patterns/scheduling.mdx +23 -23
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +26 -26
- package/docs/cookbook/common-patterns/timeouts.mdx +23 -23
- package/docs/cookbook/common-patterns/webhooks.mdx +6 -6
- package/docs/cookbook/common-patterns/workflow-composition.mdx +19 -19
- package/docs/cookbook/index.mdx +22 -22
- package/docs/cookbook/integrations/ai-sdk.mdx +43 -41
- package/docs/cookbook/integrations/chat-sdk.mdx +34 -34
- package/docs/cookbook/integrations/sandbox.mdx +46 -46
- package/docs/deploying.mdx +15 -15
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +12 -12
- package/docs/errors/corrupted-event-log.mdx +11 -11
- package/docs/errors/deployment-mismatch.mdx +14 -14
- package/docs/errors/fetch-in-workflow.mdx +8 -8
- package/docs/errors/hook-conflict.mdx +11 -11
- package/docs/errors/index.mdx +1 -1
- package/docs/errors/node-js-module-in-workflow.mdx +5 -5
- package/docs/errors/replay-divergence.mdx +2 -2
- package/docs/errors/runtime-decryption-failed.mdx +12 -12
- package/docs/errors/serialization-failed.mdx +40 -12
- package/docs/errors/start-invalid-workflow-function.mdx +5 -5
- package/docs/errors/step-executed-multiple-times.mdx +2 -2
- package/docs/errors/step-not-registered.mdx +5 -5
- package/docs/errors/timeout-in-workflow.mdx +8 -8
- package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
- package/docs/errors/webhook-response-not-sent.mdx +16 -16
- package/docs/errors/workflow-not-registered.mdx +5 -5
- package/docs/foundations/cancellation.mdx +31 -31
- package/docs/foundations/errors-and-retries.mdx +42 -11
- package/docs/foundations/hooks.mdx +35 -35
- package/docs/foundations/idempotency.mdx +9 -9
- package/docs/foundations/serialization.mdx +21 -22
- package/docs/foundations/starting-workflows.mdx +36 -37
- package/docs/foundations/streaming.mdx +46 -41
- package/docs/foundations/versioning.mdx +3 -3
- package/docs/foundations/workflows-and-steps.mdx +9 -9
- package/docs/getting-started/astro.mdx +16 -16
- package/docs/getting-started/express.mdx +8 -8
- package/docs/getting-started/fastify.mdx +8 -8
- package/docs/getting-started/hono.mdx +8 -8
- package/docs/getting-started/nestjs.mdx +18 -17
- package/docs/getting-started/next.mdx +11 -11
- package/docs/getting-started/nitro.mdx +16 -16
- package/docs/getting-started/nuxt.mdx +8 -8
- package/docs/getting-started/python.mdx +4 -4
- package/docs/getting-started/react-router/v7.mdx +1 -1
- package/docs/getting-started/react-router/v8.mdx +1 -1
- package/docs/getting-started/sveltekit.mdx +14 -14
- package/docs/getting-started/tanstack-start.mdx +12 -12
- package/docs/getting-started/vite.mdx +8 -8
- package/docs/how-it-works/cancellation.mdx +62 -62
- package/docs/how-it-works/code-transform.mdx +66 -54
- package/docs/how-it-works/encryption.mdx +25 -21
- package/docs/how-it-works/event-sourcing.mdx +53 -35
- package/docs/how-it-works/framework-integrations.mdx +12 -12
- package/docs/how-it-works/understanding-directives.mdx +21 -21
- package/docs/internal/index.mdx +6 -6
- package/docs/internal/nitro-native-build.mdx +2 -2
- package/docs/internal/nitro-web-ui.mdx +4 -4
- package/docs/internal/serializable-abort-controller.mdx +7 -7
- package/docs/observability/attributes.mdx +3 -3
- package/docs/observability/index.mdx +14 -10
- package/docs/observability/tracing.mdx +10 -10
- package/docs/testing/index.mdx +33 -33
- package/docs/testing/server-based.mdx +10 -10
- package/docs/whats-new.mdx +185 -0
- package/package.json +12 -12
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Sandbox
|
|
3
|
-
description: Model one Vercel Sandbox per workflow run
|
|
3
|
+
description: Model one Vercel Sandbox per workflow run, durable, idle-efficient, and not bound by the 5-hour sandbox hard cap.
|
|
4
4
|
type: guide
|
|
5
|
-
summary: Own a sandbox for the lifetime of a workflow run. Hibernate on idle via snapshot(), proactively refresh before the sandbox hard cap, and reconnect by runId
|
|
5
|
+
summary: Own a sandbox for the lifetime of a workflow run. Hibernate on idle via snapshot(), proactively refresh before the sandbox hard cap, and reconnect by `runId`, so one logical session can run effectively forever.
|
|
6
6
|
related:
|
|
7
7
|
- /docs/ai/defining-tools
|
|
8
8
|
- /docs/foundations/errors-and-retries
|
|
@@ -14,34 +14,34 @@ related:
|
|
|
14
14
|
text="Implement a durable Vercel Sandbox-backed coding-agent workflow. Install the Sandbox package used by this project and `workflow`. Create an exported workflow function with "use workflow" that owns the agent session. Put sandbox creation, command execution, snapshot, refresh, and cleanup into helper functions with "use step". Persist the sandbox ID, snapshot ID, and workflow run ID in the project data store so clients can reconnect. Use `getWritable()` from `workflow` to stream agent progress and command output. Use `sleep()` to hibernate, refresh, or enforce idle timeouts. Add API routes to start a session, reconnect by run ID, and stop/cleanup. Verify first run, reconnect after reload, snapshot restore, timeout, and cleanup behavior."
|
|
15
15
|
/>
|
|
16
16
|
|
|
17
|
-
[Vercel Sandbox](https://vercel.com/docs/sandbox) provides isolated code execution environments. The `@vercel/sandbox` package has first-class support for the Workflow SDK
|
|
17
|
+
[Vercel Sandbox](https://vercel.com/docs/sandbox) provides isolated code execution environments. The `@vercel/sandbox` package has first-class support for the Workflow SDK: the `Sandbox` class is serializable, and its methods (`create`, `runCommand`, `stop`, `snapshot`) implicitly run as steps. You can use `Sandbox` directly inside a workflow function without wrapping each call in a separate `"use step"` function.
|
|
18
18
|
|
|
19
19
|
## Why Workflow + Sandbox
|
|
20
20
|
|
|
21
|
-
A sandbox alone
|
|
21
|
+
A sandbox alone provides an isolated virtual machine (VM). A workflow provides a **durable controller** for that VM's entire lifetime:
|
|
22
22
|
|
|
23
23
|
- **One workflow run = one sandbox session.** The `runId` is the only state you need to persist on the client. Close the tab, come back a week later, POST the same `runId` and you're back in the same session.
|
|
24
|
-
- **Efficient resource use.** Active sandboxes cost money; hibernated workflows cost nothing. The workflow races a command hook against a `sleep()` timer
|
|
25
|
-
- **Beyond the 5-hour hard cap.** Every Vercel Sandbox has a maximum lifetime. The workflow tracks that deadline and proactively
|
|
24
|
+
- **Efficient resource use.** Active sandboxes cost money; hibernated workflows cost nothing. The workflow races a command hook against a `sleep()` timer. When idle, it calls `sandbox.snapshot()` (which also stops the VM) and waits indefinitely. When the next command arrives, the workflow starts a new sandbox from the snapshot with the filesystem, installed packages, and git history intact.
|
|
25
|
+
- **Beyond the 5-hour hard cap.** Every Vercel Sandbox has a maximum lifetime. The workflow tracks that deadline and proactively creates a snapshot and replacement before the cap, so the logical session outlives any one VM. This provides an effectively unbounded session on time-bounded infrastructure.
|
|
26
26
|
- **Automatic cleanup.** `try/finally` in the workflow guarantees the VM is stopped on failure or destroy.
|
|
27
27
|
|
|
28
28
|
<Callout type="info">
|
|
29
29
|
An effectively unbounded sandbox session is still one workflow run, so it stays on the deployment that started it. If the controller or agent code should upgrade over time, use an explicit version boundary and pass the serialized state or stream handles forward. See [Versioning](/docs/foundations/versioning).
|
|
30
30
|
</Callout>
|
|
31
31
|
|
|
32
|
-
## Use
|
|
32
|
+
## Use case: coding agents
|
|
33
33
|
|
|
34
|
-
This is the pattern [Open Agents](https://open-agents.dev/) uses to spawn coding agents that run "infinitely in the cloud." Each agent session gets its own sandbox
|
|
34
|
+
This is the pattern [Open Agents](https://open-agents.dev/) uses to spawn coding agents that run "infinitely in the cloud." Each agent session gets its own sandbox (full filesystem, network, and runtime access), and the durable workflow keeps the agent loop resumable across restarts, auto-hibernates when the user walks away, and reconnects instantly when they return.
|
|
35
35
|
|
|
36
36
|
Most coding-agent workloads look like this:
|
|
37
37
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
38
|
+
- The user sends a task, and the agent plans, reads files, runs shell commands, and commits.
|
|
39
|
+
- If the user leaves mid-run, the agent continues and eventually waits for input.
|
|
40
|
+
- When the user returns, the same branch, filesystem, and conversation history remain available.
|
|
41
41
|
|
|
42
|
-
Without durable workflows you'd need a separate state store for the agent loop, a
|
|
42
|
+
Without durable workflows, you'd need a separate state store for the agent loop, a job queue for retries, a scheduler for idle cleanup, and custom reconnection logic. The pattern below keeps the workflow controller in one file.
|
|
43
43
|
|
|
44
|
-
## Quickstart:
|
|
44
|
+
## Quickstart: one-shot pipeline
|
|
45
45
|
|
|
46
46
|
Before the full session pattern, the simplest shape. Each sandbox method is an implicit step, so the event log records every command and the workflow replays from the last completed call on restart.
|
|
47
47
|
|
|
@@ -74,17 +74,17 @@ export async function sandboxPipeline(input: { commands: string[] }) {
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
## Session
|
|
77
|
+
## Session pattern: persistent sandbox beyond the hard cap
|
|
78
78
|
|
|
79
79
|
One workflow run owns a sandbox for its whole lifetime. The workflow's loop does two jobs simultaneously:
|
|
80
80
|
|
|
81
|
-
1. **Command pipeline
|
|
82
|
-
2. **Sandbox lifecycle
|
|
81
|
+
1. **Command pipeline**: await a hook, run the next user command, stream output, loop.
|
|
82
|
+
2. **Sandbox lifecycle**: race the hook against a `sleep()` timer armed for whichever comes first: the idle deadline or the sandbox's refresh deadline (a safety margin before its hard cap).
|
|
83
83
|
|
|
84
84
|
When the timer wins:
|
|
85
85
|
|
|
86
|
-
- **Idle
|
|
87
|
-
- **Near sandbox hard cap
|
|
86
|
+
- **Idle**: Call `sandbox.snapshot()` and wait indefinitely for the next command. The workflow uses no compute while suspended.
|
|
87
|
+
- **Near the sandbox hard cap**: Call `sandbox.snapshot()` and immediately create a new sandbox from the snapshot. The session remains continuous while the underlying VM rotates.
|
|
88
88
|
|
|
89
89
|
The only way out is an explicit `/destroy` command.
|
|
90
90
|
|
|
@@ -170,7 +170,7 @@ export async function sandboxSessionWorkflow() {
|
|
|
170
170
|
"use workflow";
|
|
171
171
|
|
|
172
172
|
const { workflowRunId } = getWorkflowMetadata();
|
|
173
|
-
// Create the hook once, outside the loop
|
|
173
|
+
// Create the hook once, outside the loop: reusing the same token from inside // [!code highlight]
|
|
174
174
|
// the loop would throw HookConflictError. // [!code highlight]
|
|
175
175
|
const hook = commandHook.create({ token: workflowRunId });
|
|
176
176
|
|
|
@@ -205,8 +205,8 @@ export async function sandboxSessionWorkflow() {
|
|
|
205
205
|
try {
|
|
206
206
|
while (!destroyed) {
|
|
207
207
|
if (hibernated && snapshot) {
|
|
208
|
-
// While hibernated, the VM is already stopped.
|
|
209
|
-
// command
|
|
208
|
+
// While hibernated, the VM is already stopped. Wait for the next
|
|
209
|
+
// command: no idle timer, no compute cost.
|
|
210
210
|
const payload = await hook;
|
|
211
211
|
if (payload.command === "/destroy") { destroyed = true; break; }
|
|
212
212
|
|
|
@@ -231,7 +231,7 @@ export async function sandboxSessionWorkflow() {
|
|
|
231
231
|
continue;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
// Active
|
|
234
|
+
// Active. Wake at whichever comes first: idle-deadline or refresh-deadline.
|
|
235
235
|
const idleDeadline = lastActivityAt + HIBERNATE_AFTER_MS;
|
|
236
236
|
const refreshDeadline = sandboxExpiresAt - REFRESH_SAFETY_MS;
|
|
237
237
|
const wakeAt = Math.min(idleDeadline, refreshDeadline);
|
|
@@ -246,7 +246,7 @@ export async function sandboxSessionWorkflow() {
|
|
|
246
246
|
const nearExpiry = Date.now() >= refreshDeadline;
|
|
247
247
|
|
|
248
248
|
if (nearExpiry) {
|
|
249
|
-
// Proactive refresh
|
|
249
|
+
// Proactive refresh: snapshot and immediately recreate so the
|
|
250
250
|
// session outlives the sandbox hard cap.
|
|
251
251
|
await emit({ type: "status", state: "refreshing", at: Date.now() });
|
|
252
252
|
const snap = await sandbox.snapshot(); // [!code highlight]
|
|
@@ -263,7 +263,7 @@ export async function sandboxSessionWorkflow() {
|
|
|
263
263
|
});
|
|
264
264
|
lastActivityAt = Date.now();
|
|
265
265
|
} else {
|
|
266
|
-
// Idle
|
|
266
|
+
// Idle: snapshot and hibernate indefinitely.
|
|
267
267
|
await emit({ type: "status", state: "hibernating", at: Date.now() });
|
|
268
268
|
snapshot = await sandbox.snapshot(); // [!code highlight]
|
|
269
269
|
hibernated = true;
|
|
@@ -302,7 +302,7 @@ export async function sandboxSessionWorkflow() {
|
|
|
302
302
|
|
|
303
303
|
<Tab value="API Routes">
|
|
304
304
|
|
|
305
|
-
Two endpoints. `/start` accepts an optional `{ runId }
|
|
305
|
+
Two endpoints manage the session. `/start` accepts an optional `{ runId }`: if the run still exists, it replays the event log from index 0 so a returning client fully rehydrates. `/command` resumes the hook and returns immediately; command output lands on the `/start` stream.
|
|
306
306
|
|
|
307
307
|
<Callout type="info">
|
|
308
308
|
This example starts a fresh sandbox session when no `runId` is provided. If your product needs one sandbox session per user, project, or task, use a deterministic hook token derived from that session key and route retries through the active hook. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
|
|
@@ -334,7 +334,7 @@ export async function POST(req: Request) {
|
|
|
334
334
|
},
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
|
-
// Stale runId
|
|
337
|
+
// Stale runId: fall through to start fresh.
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
const run = await start(sandboxSessionWorkflow, []);
|
|
@@ -383,7 +383,7 @@ export async function POST(req: Request) {
|
|
|
383
383
|
|
|
384
384
|
<Tab value="Client">
|
|
385
385
|
|
|
386
|
-
On mount,
|
|
386
|
+
On mount, reconnect to the existing run if `localStorage` contains a `runId`. Otherwise, start a new run. Send commands to `/command` with POST requests. Output arrives on the `/start` stream.
|
|
387
387
|
|
|
388
388
|
```tsx title="components/sandbox-runner.tsx" lineNumbers
|
|
389
389
|
"use client";
|
|
@@ -476,22 +476,22 @@ export function SandboxRunner() {
|
|
|
476
476
|
|
|
477
477
|
</Tabs>
|
|
478
478
|
|
|
479
|
-
## How
|
|
479
|
+
## How it works
|
|
480
480
|
|
|
481
|
-
1. **One workflow
|
|
482
|
-
2. **
|
|
483
|
-
3. **Two timer branches
|
|
484
|
-
4. **Proactive refresh
|
|
485
|
-
5. **`sandbox.snapshot()` stops the VM
|
|
486
|
-
6. **Resume
|
|
487
|
-
7. **Reconnect by runId
|
|
488
|
-
8. **Exit only on `/destroy
|
|
481
|
+
1. **One workflow represents one session**: The workflow owns a sandbox for its entire lifetime. The `runId` is the only state the client has to remember.
|
|
482
|
+
2. **Create the hook once**: Call `commandHook.create({ token: workflowRunId })` outside the loop. Creating it twice with the same token throws `HookConflictError`.
|
|
483
|
+
3. **Two timer branches control wake-up**: The active-state race wakes on the earlier of `idleDeadline` and `refreshDeadline`. The hibernated state awaits the hook alone, with no timer or compute.
|
|
484
|
+
4. **Proactive refresh replaces the sandbox**: When `refreshDeadline = sandboxExpiresAt - REFRESH_SAFETY_MS` arrives, the workflow takes a snapshot and immediately creates a new sandbox from it. This rolls over the hard cap without user intervention.
|
|
485
|
+
5. **`sandbox.snapshot()` stops the VM**: The snapshot process stops the VM, so don't call `stop()` separately.
|
|
486
|
+
6. **Resume creates a new sandbox**: `Sandbox.create({ source: { type: "snapshot", snapshotId } })` creates a new VM from the snapshot. The new sandbox has a different `sandboxId`; the filesystem, installed packages, and git history are preserved.
|
|
487
|
+
7. **Reconnect by `runId`**: `getRun(runId).getReadable({ startIndex: 0 })` replays the durable event log to a returning client, which rebuilds UI state from the replay.
|
|
488
|
+
8. **Exit only on `/destroy`**: The workflow loop has no hard deadline of its own. Individual sandboxes time out, but the session doesn't.
|
|
489
489
|
|
|
490
490
|
## Pitfalls
|
|
491
491
|
|
|
492
492
|
### `sandbox.stop()` is terminal
|
|
493
493
|
|
|
494
|
-
A stopped sandbox cannot be restarted
|
|
494
|
+
A stopped sandbox cannot be restarted: you have to create a new one. Hibernation is only possible via `snapshot()` + new-sandbox-from-snapshot. Don't try to "pause" an active sandbox with `stop()` and resume later.
|
|
495
495
|
|
|
496
496
|
### `snapshot()` already stops the VM
|
|
497
497
|
|
|
@@ -503,7 +503,7 @@ Both `resuming` (idle → command) and `refreshing` (near-hard-cap rotation) cre
|
|
|
503
503
|
|
|
504
504
|
### Keep the refresh margin generous
|
|
505
505
|
|
|
506
|
-
`snapshot()`
|
|
506
|
+
`snapshot()` followed by `Sandbox.create({ source })` takes time, typically tens of seconds. If `REFRESH_SAFETY_MS` is too small, the old sandbox hits its hard cap mid-snapshot. Leave at least 60–90 seconds; the example uses 5 minutes.
|
|
507
507
|
|
|
508
508
|
### Don't call `writable.close()` inside a workflow function
|
|
509
509
|
|
|
@@ -523,11 +523,11 @@ Each iteration's `hook.then(...)` attaches a listener to the same hook instance.
|
|
|
523
523
|
|
|
524
524
|
## Key APIs
|
|
525
525
|
|
|
526
|
-
- [`Sandbox.create`](https://vercel.com/docs/sandbox)
|
|
527
|
-
- [`sandbox.runCommand`](https://vercel.com/docs/sandbox)
|
|
528
|
-
- [`sandbox.snapshot`](https://vercel.com/docs/sandbox)
|
|
529
|
-
- [`defineHook()`](/docs/api-reference/workflow/define-hook)
|
|
530
|
-
- [`sleep()`](/docs/api-reference/workflow/sleep)
|
|
531
|
-
- [`getRun()`](/docs/api-reference/workflow-api/get-run)
|
|
532
|
-
- [`getWritable()`](/docs/api-reference/workflow/get-writable)
|
|
533
|
-
- [Idempotency](/docs/foundations/idempotency)
|
|
526
|
+
- [`Sandbox.create`](https://vercel.com/docs/sandbox): provision a VM (runtime, source, timeout)
|
|
527
|
+
- [`sandbox.runCommand`](https://vercel.com/docs/sandbox): execute a command; implicit step
|
|
528
|
+
- [`sandbox.snapshot`](https://vercel.com/docs/sandbox): save state and stop the VM; returns `Snapshot`
|
|
529
|
+
- [`defineHook()`](/docs/api-reference/workflow/define-hook): suspension point for user commands
|
|
530
|
+
- [`sleep()`](/docs/api-reference/workflow/sleep): durable timer that powers both idle hibernation and proactive refresh
|
|
531
|
+
- [`getRun()`](/docs/api-reference/workflow-api/get-run): look up a run and replay its event log for reconnection
|
|
532
|
+
- [`getWritable()`](/docs/api-reference/workflow/get-writable): resumable newline-delimited JSON (NDJSON) event stream
|
|
533
|
+
- [Idempotency](/docs/foundations/idempotency): choose when `/start` should reuse an existing run
|
package/docs/deploying.mdx
CHANGED
|
@@ -10,14 +10,14 @@ related:
|
|
|
10
10
|
- /worlds/building-a-world
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The same workflow code can run locally during development, on Vercel with zero configuration, or on any infrastructure using **Worlds**, which are pluggable adapters that handle storage, queuing, and communication.
|
|
14
14
|
|
|
15
|
-
## Local
|
|
15
|
+
## Local development
|
|
16
16
|
|
|
17
|
-
During local development, workflows
|
|
17
|
+
During local development, workflows use the **Local World** without configuration. The Local World stores workflow data in a `.workflow-data/` directory and processes steps synchronously for development and testing.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
#
|
|
20
|
+
# Run your dev server to use workflows locally
|
|
21
21
|
npm run dev
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -33,17 +33,17 @@ npx workflow inspect runs
|
|
|
33
33
|
|
|
34
34
|
## Deploying to Vercel
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Deploy workflows to production on Vercel without configuration. Workflows deployed to Vercel use the **Vercel World**.
|
|
37
37
|
|
|
38
38
|
The Vercel World provides:
|
|
39
39
|
|
|
40
|
-
- **Durable storage
|
|
41
|
-
- **Managed queuing
|
|
42
|
-
- **Automatic scaling
|
|
43
|
-
- **Built-in observability
|
|
44
|
-
- **Multi-region
|
|
40
|
+
- **Durable storage**: Workflow state persists across function invocations
|
|
41
|
+
- **Managed queuing**: The queue processes steps reliably with automatic retries
|
|
42
|
+
- **Automatic scaling**: Workflows scale with your application
|
|
43
|
+
- **Built-in observability**: View workflow runs in the Vercel dashboard
|
|
44
|
+
- **Multi-region**: The region that creates each run also hosts its workflow data, queue, and streams, keeping them close to your users
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Deploy your application:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
vercel deploy
|
|
@@ -55,7 +55,7 @@ vercel deploy
|
|
|
55
55
|
Learn more about the [Vercel World](/worlds/vercel) and its capabilities, including [multi-region](/worlds/vercel#multi-region).
|
|
56
56
|
</Callout>
|
|
57
57
|
|
|
58
|
-
## Self-
|
|
58
|
+
## Self-hosting & other providers
|
|
59
59
|
|
|
60
60
|
For self-hosting or deploying to other cloud providers, you can use community-maintained Worlds or build your own.
|
|
61
61
|
|
|
@@ -68,7 +68,7 @@ For self-hosting or deploying to other cloud providers, you can use community-ma
|
|
|
68
68
|
</Card>
|
|
69
69
|
</Cards>
|
|
70
70
|
|
|
71
|
-
### Using a
|
|
71
|
+
### Using a third-party World
|
|
72
72
|
|
|
73
73
|
To use a different World implementation, set the `WORKFLOW_TARGET_WORLD` environment variable:
|
|
74
74
|
|
|
@@ -78,11 +78,11 @@ export WORKFLOW_TARGET_WORLD=@workflow/world-postgres
|
|
|
78
78
|
export DATABASE_URL=postgres://...
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Each World may have its own configuration requirements
|
|
81
|
+
Each World may have its own configuration requirements. Refer to that World's documentation for details.
|
|
82
82
|
|
|
83
83
|
## Observability
|
|
84
84
|
|
|
85
|
-
The [Observability tools](/docs/observability) work with any World backend. By default they connect to your local environment, but can
|
|
85
|
+
The [Observability tools](/docs/observability) work with any World backend. By default, they connect to your local environment, but you can configure them to inspect remote deployments:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
# Inspect local workflows
|
|
@@ -17,21 +17,21 @@ related:
|
|
|
17
17
|
|
|
18
18
|
## Error
|
|
19
19
|
|
|
20
|
-
```
|
|
20
|
+
```text
|
|
21
21
|
AbortSignal.timeout() is not supported in workflow functions.
|
|
22
22
|
Use sleep() with an AbortController instead.
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
## Why
|
|
25
|
+
## Why this happens
|
|
26
26
|
|
|
27
|
-
`AbortSignal.timeout()` creates a signal that aborts after a real-time delay using an internal timer. Workflow functions must be [deterministic](/docs/foundations/workflows-and-steps) to support replay
|
|
27
|
+
`AbortSignal.timeout()` creates a signal that aborts after a real-time delay using an internal timer. Workflow functions must be [deterministic](/docs/foundations/workflows-and-steps) to support replay: they run the same code multiple times during the workflow's lifecycle, using the [event log](/docs/how-it-works/event-sourcing) to resume execution to the correct point.
|
|
28
28
|
|
|
29
29
|
Real-time timers break this determinism because:
|
|
30
|
-
- On the first execution, the timer might fire after 10 seconds
|
|
31
|
-
- On replay, the timer would fire again, but the event log may have already advanced past that point
|
|
32
|
-
- The timer's behavior depends on wall-clock time, which varies between executions
|
|
30
|
+
- On the first execution, the timer might fire after 10 seconds.
|
|
31
|
+
- On replay, the timer would fire again, but the event log may have already advanced past that point.
|
|
32
|
+
- The timer's behavior depends on wall-clock time, which varies between executions.
|
|
33
33
|
|
|
34
|
-
## How to
|
|
34
|
+
## How to fix
|
|
35
35
|
|
|
36
36
|
Use [`sleep()`](/docs/api-reference/workflow/sleep) with an `AbortController` to create a deterministic timeout that cancels in-flight work:
|
|
37
37
|
|
|
@@ -70,7 +70,7 @@ async function fetchData(signal: AbortSignal) {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
The `sleep()`
|
|
73
|
+
The `sleep()` and `AbortController` pattern is the durable equivalent of `AbortSignal.timeout()`. The sleep is recorded in the event log, so it replays deterministically. If `fetchData` finishes within 10 seconds, you get the response. Otherwise, the timer calls `controller.abort()`, `fetch` rejects with an `AbortError`, and the step's failure propagates to the workflow as a `FatalError` (no retries because the abort is intentional cancellation).
|
|
74
74
|
|
|
75
75
|
<Callout type="info">
|
|
76
76
|
`AbortSignal.timeout()` works normally inside step functions, since steps have full Node.js runtime access and are not replayed.
|
|
@@ -78,7 +78,7 @@ The `sleep()` + `AbortController` pattern is the durable equivalent of `AbortSig
|
|
|
78
78
|
|
|
79
79
|
## Related
|
|
80
80
|
|
|
81
|
-
- [Cancellation](/docs/foundations/cancellation)
|
|
82
|
-
- [`sleep()` API Reference](/docs/api-reference/workflow/sleep)
|
|
83
|
-
- [Workflows and Steps](/docs/foundations/workflows-and-steps)
|
|
84
|
-
- [`setTimeout` in Workflow](/docs/errors/timeout-in-workflow)
|
|
81
|
+
- [Cancellation](/docs/foundations/cancellation): Patterns for canceling in-flight work
|
|
82
|
+
- [`sleep()` API Reference](/docs/api-reference/workflow/sleep): Durable sleep primitive
|
|
83
|
+
- [Workflows and Steps](/docs/foundations/workflows-and-steps): Why workflow functions must be deterministic
|
|
84
|
+
- [`setTimeout` in Workflow](/docs/errors/timeout-in-workflow): Similar restriction on `setTimeout`
|
|
@@ -13,15 +13,15 @@ This error occurs when the Workflow runtime repeatedly cannot replay events in t
|
|
|
13
13
|
|
|
14
14
|
This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime first retries transient replay divergence automatically; it marks the run as failed with this error only after replay still cannot recover.
|
|
15
15
|
|
|
16
|
-
## Error
|
|
16
|
+
## Error message
|
|
17
17
|
|
|
18
|
-
```
|
|
18
|
+
```text
|
|
19
19
|
Workflow replay diverged <divergenceCount> times after <maxRecoveryReplays> recovery replays; latest divergent event was <eventId>. Last divergence: <details>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
## Why
|
|
22
|
+
## Why this happens
|
|
23
23
|
|
|
24
|
-
Workflows persist their progress as an ordered event log. During replay, the runtime processes each event in sequence
|
|
24
|
+
Workflows persist their progress as an ordered event log. During replay, the runtime processes each event in sequence. Every event must be consumed by a matching callback, such as a step or sleep waiting for its result. An event no callback ever claims is one the runtime would have to drop to finish the run, so it fails the run instead of returning a result that silently ignored it.
|
|
25
25
|
|
|
26
26
|
A delivery written from outside the replay, such as a hook firing or a step completing on another invocation, can land ahead of the events the replay is writing itself. That is ordinary concurrency rather than corruption, so the runtime holds such an event and offers it to each consumer the replay registers afterwards. The failure comes only when the workflow function returns while an event is still held, at which point no consumer can ever appear. A replay that suspends still holding one reports it on the span (`workflow.events.parked.count`, `.event_id`, `.event_type`) and leaves the decision to the replay that follows.
|
|
27
27
|
|
|
@@ -29,13 +29,13 @@ Before failing, the runtime retries a divergent replay and surfaces this termina
|
|
|
29
29
|
|
|
30
30
|
Common scenarios that produce this error:
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
- **An unclaimed event that repeats nothing**: A duplicate of a kind the log already records for that entity is read past rather than failing the run, so a second `step_completed` or `wait_completed` is not this error (see [Duplicate Events](/docs/how-it-works/event-sourcing#duplicate-events)). What fails is an unclaimed event with no earlier counterpart to defer to: a `step_started` behind a `step_completed` on a log that never recorded a `step_started`, for instance. No consumer remains for the step, and there is no earlier event of that kind the replay could be reading instead.
|
|
33
|
+
- **Orphaned events**: A `step_completed` or `wait_completed` event whose `correlationId` doesn't match any step or sleep in the workflow code, so the replay reaches its end still holding it.
|
|
34
|
+
- **A hole in the log**: Events are numbered by their position in the run's log, and those positions are dense, so a position below the log's highest that holds no event means the log the replay loaded is incomplete. The runtime cannot tell a position no write ever occupied from one whose event it failed to read, so it refuses to replay rather than produce a result that may be silently wrong. See [`WORKFLOW_SLOT_GAP_CHECK`](/docs/configuration/runtime-tuning#workflow_slot_gap_check).
|
|
35
35
|
|
|
36
|
-
## What
|
|
36
|
+
## What to do
|
|
37
37
|
|
|
38
|
-
This error indicates a bug in the Workflow SDK or Workflow server
|
|
38
|
+
This error indicates a bug in the Workflow SDK or Workflow server, not in your workflow code. Your workflow code does not need to change. Follow these steps to resolve the issue:
|
|
39
39
|
|
|
40
40
|
### 1. Upgrade to the latest `workflow` package
|
|
41
41
|
|
|
@@ -51,14 +51,14 @@ If this error is displayed, automatic replay recovery has already been exhausted
|
|
|
51
51
|
|
|
52
52
|
### 3. Report the issue
|
|
53
53
|
|
|
54
|
-
If the error persists after upgrading,
|
|
54
|
+
If the error persists after upgrading, [open an issue on GitHub](https://github.com/vercel/workflow/issues/new) so we can investigate and fix the underlying bug. Include the following details to help us diagnose the problem:
|
|
55
55
|
|
|
56
56
|
- The version of the `workflow` package you are using
|
|
57
57
|
- The run ID(s) of the affected workflow run(s)
|
|
58
58
|
- The error message (including `eventType`, `correlationId`, and `eventId`)
|
|
59
59
|
- Any details about the event log or the workflow that triggered the error
|
|
60
60
|
|
|
61
|
-
## This
|
|
61
|
+
## This error cannot be caught
|
|
62
62
|
|
|
63
63
|
Unlike other workflow errors, a corrupted event log error is **not catchable** inside your workflow function. Because the event log itself is invalid, the runtime cannot safely continue executing any user code. The entire run fails immediately and is marked as `failed`.
|
|
64
64
|
|
|
@@ -15,30 +15,30 @@ Every run is pinned to a single deployment when it starts. When a queued workflo
|
|
|
15
15
|
|
|
16
16
|
This is an SDK/runtime signal, not an error thrown by your workflow code, and it is not catchable inside a workflow function.
|
|
17
17
|
|
|
18
|
-
## Error
|
|
18
|
+
## Error message
|
|
19
19
|
|
|
20
|
-
```
|
|
20
|
+
```text
|
|
21
21
|
Workflow run "wrun_..." is pinned to deployment "dpl_A", but was received by deployment "dpl_B". The runtime re-routed the message to "dpl_A" 3 times and it kept arriving elsewhere, so the run was stopped to protect against code-skew errors. Verify that the run's deployment is still available and that queue callbacks are routed to it.
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
When the queue definitively reports that the run's deployment cannot be reached
|
|
24
|
+
When the queue definitively reports that the run's deployment cannot be reached (it was deleted, or aged out of its retention window), no re-route is possible and the message omits the re-routing clause. Transient or unknown publishing failures leave the current delivery unacknowledged so the queue can redeliver it; they do not fail the run or consume this recovery budget.
|
|
25
25
|
|
|
26
|
-
## Why
|
|
26
|
+
## Why a run is pinned
|
|
27
27
|
|
|
28
28
|
A run's deployment is chosen once, at [`start()`](/docs/api-reference/workflow-api/start):
|
|
29
29
|
|
|
30
|
-
- By default it is the deployment that called `start()
|
|
30
|
+
- By default it is the deployment that called `start()`. See [Versioning](/docs/foundations/versioning) for why runs are pinned this way.
|
|
31
31
|
- With `start(workflow, args, { deploymentId })` it is the id you pass, so a run can deliberately target a deployment other than the one that created it.
|
|
32
32
|
- With `deploymentId: "latest"` it is the most recent deployment for the current environment, resolved at start time.
|
|
33
33
|
|
|
34
34
|
Whichever it is, that `deploymentId` is recorded on the run, and every subsequent workflow replay and step execution must happen on that deployment. Continuing on a different one is unsafe:
|
|
35
35
|
|
|
36
36
|
1. **Code skew.** The workflow and step bundles on the receiving deployment may not match the code that produced the run's recorded history, so replay could diverge or produce incorrect results.
|
|
37
|
-
2. **Encryption.** Step inputs and other event-log payloads are encrypted with a per-run key derived from the pinned deployment's key material. A different deployment derives the wrong key and cannot decrypt them
|
|
37
|
+
2. **Encryption.** Step inputs and other event-log payloads are encrypted with a per-run key derived from the pinned deployment's key material. A different deployment derives the wrong key and cannot decrypt them, previously the source of a confusing [runtime-decryption-failed](/docs/errors/runtime-decryption-failed) that exhausted retries with no clear cause.
|
|
38
38
|
|
|
39
|
-
So the runtime checks the pinned deployment before it executes anything, and `DEPLOYMENT_MISMATCH` names the result
|
|
39
|
+
So the runtime checks the pinned deployment before it executes anything, and `DEPLOYMENT_MISMATCH` names the result, instead of the mismatch surfacing later as an unrelated decryption failure.
|
|
40
40
|
|
|
41
|
-
## Automatic
|
|
41
|
+
## Automatic recovery
|
|
42
42
|
|
|
43
43
|
A deployment that receives a run it does not own first tries to fix the delivery rather than fail the run:
|
|
44
44
|
|
|
@@ -46,19 +46,19 @@ A deployment that receives a run it does not own first tries to fix the delivery
|
|
|
46
46
|
2. Delivery is delayed with a short exponential backoff (1s, 2s, 4s).
|
|
47
47
|
3. If the run keeps arriving at the wrong deployment, the run is failed with `DEPLOYMENT_MISMATCH` after `WORKFLOW_DEPLOYMENT_MISMATCH_MAX_RETRIES` attempts (default `3`). Set it to `0` to fail on the first misrouted delivery instead.
|
|
48
48
|
|
|
49
|
-
Nothing is executed on the wrong deployment during recovery: no workflow code, no step body, no `step_started`, and no hook resume. Whatever the delivery was carrying travels with it, so a pending step keeps its identity and a hook resume keeps its payload
|
|
49
|
+
Nothing is executed on the wrong deployment during recovery: no workflow code, no step body, no `step_started`, and no hook resume. Whatever the delivery was carrying travels with it, so a pending step keeps its identity and a hook resume keeps its payload: they run on the deployment that can actually decrypt them.
|
|
50
50
|
|
|
51
51
|
Recovery attempts do not create events on the run, so a run that self-heals looks completely normal. They are reported on the invocation's trace span (`workflow.deployment.pinned_id`, `workflow.deployment_mismatch.retry_count`, `workflow.deployment_mismatch.recovered`) and as a runtime warning in your function logs.
|
|
52
52
|
|
|
53
|
-
## What
|
|
53
|
+
## What to do
|
|
54
54
|
|
|
55
55
|
- **Re-run from the current deployment.** Trigger the workflow again from your latest deployment (or use the **Re-run** button in the Workflow Dashboard). The new run is pinned to the current deployment.
|
|
56
|
-
- **Keep a run's deployment available** for the lifetime of that run. A run whose deployment has been deleted or has aged out cannot be resumed and must be re-run
|
|
57
|
-
- **Report it** if the pinned deployment was still available. Include both deployment
|
|
56
|
+
- **Keep a run's deployment available** for the lifetime of that run. A run whose deployment has been deleted or has aged out cannot be resumed and must be re-run: recovery cannot help, so these fail on the first misrouted delivery. This applies to runs started with an explicit `deploymentId` too: pinning a run to an older deployment keeps it dependent on that deployment for its whole lifetime.
|
|
57
|
+
- **Report it** if the pinned deployment was still available. Include both deployment IDs and the run ID from the error message, plus the trace span attributes above. A run that failed this way despite a reachable target is a routing fault worth investigating rather than something to work around.
|
|
58
58
|
|
|
59
|
-
## This
|
|
59
|
+
## This error cannot be caught
|
|
60
60
|
|
|
61
|
-
Like other runtime signals, `DEPLOYMENT_MISMATCH` is **not catchable** inside your workflow function
|
|
61
|
+
Like other runtime signals, `DEPLOYMENT_MISMATCH` is **not catchable** inside your workflow function: the run is failed before any workflow or step code executes on the receiving deployment. Check the run status from outside instead:
|
|
62
62
|
|
|
63
63
|
```typescript lineNumbers
|
|
64
64
|
import { getRun } from "workflow/api";
|
|
@@ -10,24 +10,24 @@ related:
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
<CopyPrompt
|
|
13
|
-
text="Fix `fetch` usage inside workflow functions. Search workflow files for direct global `fetch(...)` calls and libraries such as AI SDK calls that use fetch. For
|
|
13
|
+
text="Fix `fetch` usage inside workflow functions. Search workflow files for direct global `fetch(...)` calls and libraries such as AI SDK calls that use fetch. For basic HTTP calls inside a `"use workflow"` function, import `{ fetch }` from `workflow` and replace the global call. For SDK/client calls that need normal Node.js or provider behavior, move the call into a helper function with `"use step"` and call that step from the workflow. Keep all step inputs and outputs serializable. Verify the workflow starts and replays without the fetch-in-workflow error."
|
|
14
14
|
/>
|
|
15
15
|
|
|
16
16
|
This error occurs when you try to use `fetch()` directly in a workflow function, or when a library (like the AI SDK) tries to call `fetch()` under the hood.
|
|
17
17
|
|
|
18
|
-
## Error
|
|
18
|
+
## Error message
|
|
19
19
|
|
|
20
|
-
```
|
|
20
|
+
```text
|
|
21
21
|
Global "fetch" is unavailable in workflow functions. Use the "fetch" step function from "workflow" to make HTTP requests.
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
## Why
|
|
24
|
+
## Why this happens
|
|
25
25
|
|
|
26
26
|
Workflow functions run in a sandboxed environment without direct access to `fetch()`.
|
|
27
27
|
|
|
28
28
|
Many libraries make HTTP requests under the hood. For example, the AI SDK's `generateText()` function calls `fetch()` to make HTTP requests to AI providers. When these libraries run inside a workflow function, they fail because the global `fetch` is not available.
|
|
29
29
|
|
|
30
|
-
## Quick
|
|
30
|
+
## Quick fix
|
|
31
31
|
|
|
32
32
|
Import the `fetch` step function from the `workflow` package and assign it to `globalThis.fetch` inside your workflow function. This version of `fetch` is a step function that wraps the standard `fetch` API, automatically handling serialization and providing retry capabilities. This will also make `fetch()` available to all functions and libraries in the current workflow function.
|
|
33
33
|
|
|
@@ -72,9 +72,9 @@ export async function chatWorkflow(prompt: string) {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
## Common
|
|
75
|
+
## Common scenarios
|
|
76
76
|
|
|
77
|
-
### AI SDK
|
|
77
|
+
### AI SDK integration
|
|
78
78
|
|
|
79
79
|
This is the most common scenario - using AI SDK functions that make HTTP requests:
|
|
80
80
|
|
|
@@ -98,7 +98,7 @@ export async function aiWorkflow(userMessage: string) {
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
### Direct API
|
|
101
|
+
### Direct API calls
|
|
102
102
|
|
|
103
103
|
You can also use the fetch step function directly for your own HTTP requests:
|
|
104
104
|
|
|
@@ -16,13 +16,13 @@ related:
|
|
|
16
16
|
|
|
17
17
|
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.
|
|
18
18
|
|
|
19
|
-
## Error
|
|
19
|
+
## Error message
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```text
|
|
22
22
|
Hook token "<token>" is already in use by another workflow
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
## Why
|
|
25
|
+
## Why this happens
|
|
26
26
|
|
|
27
27
|
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.
|
|
28
28
|
|
|
@@ -31,9 +31,9 @@ This typically happens when:
|
|
|
31
31
|
1. **Two workflows start simultaneously** with the same hardcoded token
|
|
32
32
|
2. **A previous workflow run is still waiting** for a hook when a new run tries to use the same token
|
|
33
33
|
|
|
34
|
-
## Common
|
|
34
|
+
## Common causes
|
|
35
35
|
|
|
36
|
-
### Hardcoded
|
|
36
|
+
### Hardcoded token values
|
|
37
37
|
|
|
38
38
|
{/* @skip-typecheck: incomplete code sample */}
|
|
39
39
|
```typescript lineNumbers
|
|
@@ -61,7 +61,7 @@ export async function processPayment(orderId: string) {
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
### Omitting the
|
|
64
|
+
### Omitting the token (auto-generated)
|
|
65
65
|
|
|
66
66
|
The safest approach is to let the Workflow runtime generate a unique token automatically:
|
|
67
67
|
|
|
@@ -77,7 +77,7 @@ export async function processPayment() {
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
## Handling
|
|
80
|
+
## Handling hook conflicts
|
|
81
81
|
|
|
82
82
|
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:
|
|
83
83
|
|
|
@@ -114,7 +114,7 @@ export async function processPayment(orderId: string) {
|
|
|
114
114
|
|
|
115
115
|
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.
|
|
116
116
|
|
|
117
|
-
### Delegate to the
|
|
117
|
+
### Delegate to the active Run
|
|
118
118
|
|
|
119
119
|
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:
|
|
120
120
|
|
|
@@ -156,17 +156,17 @@ export async function POST(request: Request) {
|
|
|
156
156
|
|
|
157
157
|
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.
|
|
158
158
|
|
|
159
|
-
## When
|
|
159
|
+
## When hook tokens are released
|
|
160
160
|
|
|
161
161
|
Hook tokens are automatically released when:
|
|
162
162
|
|
|
163
163
|
- The workflow run **completes** (successfully or with an error)
|
|
164
|
-
- The workflow run is **
|
|
164
|
+
- The workflow run is **canceled**
|
|
165
165
|
- The hook is explicitly **disposed**
|
|
166
166
|
|
|
167
167
|
After a workflow completes, its hook tokens become available for reuse by other workflows.
|
|
168
168
|
|
|
169
|
-
## Best
|
|
169
|
+
## Best practices
|
|
170
170
|
|
|
171
171
|
1. **Use auto-generated tokens** when possible - they are guaranteed to be unique
|
|
172
172
|
2. **Include unique identifiers** if you need custom tokens (order ID, user ID, etc.)
|
package/docs/errors/index.mdx
CHANGED
|
@@ -11,7 +11,7 @@ Fix common mistakes when creating and executing workflows in the **Workflow SDK*
|
|
|
11
11
|
|
|
12
12
|
<AutoCards />
|
|
13
13
|
|
|
14
|
-
## Learn
|
|
14
|
+
## Learn more
|
|
15
15
|
|
|
16
16
|
* [API Reference](/docs/api-reference) - Complete API documentation
|
|
17
17
|
* [Foundations](/docs/foundations) - Architecture and core concepts
|