Skip to content

docs: document run idempotency#2011

Draft
pranaygp wants to merge 15 commits into
mainfrom
pranaygp/codex/docs-run-idempotency
Draft

docs: document run idempotency#2011
pranaygp wants to merge 15 commits into
mainfrom
pranaygp/codex/docs-run-idempotency

Conversation

@pranaygp
Copy link
Copy Markdown
Contributor

Summary

  • expand the foundations and cookbook idempotency docs to cover run idempotency in addition to step side-effect idempotency
  • document that start() currently creates a new run for each call and does not accept an idempotency key
  • add cross-links/callouts from relevant API reference and cookbook pages: start(), getRun(), hook APIs, workflow fetch(), child workflows, session integrations, scheduling, timeouts, and publishing-library examples

Notes

  • Current guidance is to reserve an application/business key atomically before calling start() when duplicate run creation is unsafe.
  • Hook-token lookup is documented as useful after a run starts, but not atomic enough to replace run idempotency.

Validation

  • git diff --check
  • pnpm --filter docs lint could not run in this worktree because node_modules is missing (biome: command not found); pnpm also warned the current Node is v25.2.1 while the repo supports 18/20/22/24.
  • pnpm test:docs could not run in this worktree because node_modules is missing (vitest: command not found); same Node engine warning.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment May 20, 2026 12:04pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 20, 2026 12:04pm
example-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-astro-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-express-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-fastify-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-hono-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-nitro-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-nuxt-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-sveltekit-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workbench-vite-workflow Ready Ready Preview, Comment May 20, 2026 12:04pm
workflow-docs Ready Ready Preview, ✅ 12 resolved, Open in v0 May 20, 2026 12:04pm
workflow-swc-playground Ready Ready Preview, Comment May 20, 2026 12:04pm
workflow-tarballs Ready Ready Preview, Comment May 20, 2026 12:04pm
workflow-web Ready Ready Preview, Comment May 20, 2026 12:04pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 18, 2026

🦋 Changeset detected

Latest commit: 099876b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 1196 4 219 1419
✅ 💻 Local Development 1587 0 219 1806
✅ 📦 Local Production 1587 0 219 1806
✅ 🐘 Local Postgres 1587 0 219 1806
✅ 🪟 Windows 129 0 0 129
✅ 📋 Other 727 0 176 903
Total 6813 4 1052 7869

❌ Failed Tests

▲ Vercel Production (4 failed)

example (1 failed):

  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KS2MHW07CTE2BV0H97H5G3SM | 🔍 observability

nitro (1 failed):

  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works

nuxt (1 failed):

  • fibonacciWorkflow - recursive workflow composition via start() | wrun_01KS2MMZ63JP2GXMN2N27884EP | 🔍 observability

sveltekit (1 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 103 0 26
❌ example 102 1 26
✅ express 103 0 26
✅ fastify 103 0 26
✅ hono 103 0 26
✅ nextjs-turbopack 127 0 2
✅ nextjs-webpack 127 0 2
❌ nitro 102 1 26
❌ nuxt 102 1 26
❌ sveltekit 121 1 7
✅ vite 103 0 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 104 0 25
✅ express-stable 104 0 25
✅ fastify-stable 104 0 25
✅ hono-stable 104 0 25
✅ nextjs-turbopack-canary 110 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 129 0 0
✅ nextjs-webpack-canary 110 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 129 0 0
✅ nitro-stable 104 0 25
✅ nuxt-stable 104 0 25
✅ sveltekit-stable 123 0 6
✅ vite-stable 104 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 104 0 25
✅ express-stable 104 0 25
✅ fastify-stable 104 0 25
✅ hono-stable 104 0 25
✅ nextjs-turbopack-canary 110 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 129 0 0
✅ nextjs-webpack-canary 110 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 129 0 0
✅ nitro-stable 104 0 25
✅ nuxt-stable 104 0 25
✅ sveltekit-stable 123 0 6
✅ vite-stable 104 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 104 0 25
✅ express-stable 104 0 25
✅ fastify-stable 104 0 25
✅ hono-stable 104 0 25
✅ nextjs-turbopack-canary 110 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 129 0 0
✅ nextjs-webpack-canary 110 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 129 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 129 0 0
✅ nitro-stable 104 0 25
✅ nuxt-stable 104 0 25
✅ sveltekit-stable 123 0 6
✅ vite-stable 104 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 129 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 104 0 25
✅ e2e-local-dev-tanstack-start- 104 0 25
✅ e2e-local-postgres-nest-stable 104 0 25
✅ e2e-local-postgres-tanstack-start- 104 0 25
✅ e2e-local-prod-nest-stable 104 0 25
✅ e2e-local-prod-tanstack-start- 104 0 25
✅ e2e-vercel-prod-tanstack-start 103 0 26

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.030s (-31.1% 🟢) 1.005s (~) 0.976s 10 1.00x
💻 Local Express 0.033s (-26.0% 🟢) 1.006s (~) 0.973s 10 1.10x
🐘 Postgres Nitro 0.047s (-50.7% 🟢) 1.012s (-3.0%) 0.965s 10 1.58x
🐘 Postgres Express 0.052s (-11.0% 🟢) 1.012s (~) 0.961s 10 1.74x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.070s (-5.4% 🟢) 2.006s (~) 0.936s 10 1.00x
💻 Local Express 1.080s (-4.0%) 2.007s (~) 0.927s 10 1.01x
🐘 Postgres Express 1.084s (-5.5% 🟢) 2.010s (~) 0.926s 10 1.01x
🐘 Postgres Nitro 1.086s (-4.7%) 2.010s (~) 0.924s 10 1.02x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.406s (-4.9%) 11.022s (~) 0.617s 3 1.00x
🐘 Postgres Express 10.417s (-5.0%) 11.016s (~) 0.599s 3 1.00x
💻 Local Express 10.432s (-4.5%) 11.023s (~) 0.590s 3 1.00x
🐘 Postgres Nitro 10.445s (-3.9%) 11.017s (~) 0.573s 3 1.00x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.455s (-10.7% 🟢) 14.026s (-12.5% 🟢) 0.570s 5 1.00x
🐘 Postgres Express 13.514s (-7.3% 🟢) 14.019s (-6.7% 🟢) 0.505s 5 1.00x
🐘 Postgres Nitro 13.553s (-7.1% 🟢) 14.020s (-6.7% 🟢) 0.468s 5 1.01x
💻 Local Express 13.564s (-9.4% 🟢) 14.029s (-6.7% 🟢) 0.465s 5 1.01x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.874s (-29.2% 🟢) 12.021s (-29.4% 🟢) 0.147s 8 1.00x
🐘 Postgres Express 11.949s (-14.7% 🟢) 12.144s (-16.8% 🟢) 0.196s 8 1.01x
🐘 Postgres Nitro 12.039s (-13.8% 🟢) 12.644s (-11.6% 🟢) 0.605s 8 1.01x
💻 Local Express 12.269s (-26.1% 🟢) 13.027s (-23.5% 🟢) 0.758s 7 1.03x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.141s (-9.5% 🟢) 2.007s (~) 0.866s 15 1.00x
🐘 Postgres Nitro 1.164s (-8.7% 🟢) 2.007s (~) 0.843s 15 1.02x
💻 Local Nitro 1.178s (-27.8% 🟢) 2.006s (-3.3%) 0.828s 15 1.03x
💻 Local Express 1.227s (-17.6% 🟢) 2.008s (~) 0.781s 15 1.08x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.192s (-49.5% 🟢) 2.007s (-33.3% 🟢) 0.815s 15 1.00x
🐘 Postgres Nitro 1.200s (-49.0% 🟢) 2.007s (-33.3% 🟢) 0.807s 15 1.01x
💻 Local Nitro 1.754s (-44.2% 🟢) 2.006s (-48.4% 🟢) 0.251s 15 1.47x
💻 Local Express 2.188s (-25.9% 🟢) 2.549s (-26.2% 🟢) 0.362s 13 1.84x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.308s (-62.5% 🟢) 2.007s (-49.9% 🟢) 0.700s 15 1.00x
🐘 Postgres Nitro 1.311s (-62.3% 🟢) 2.008s (-49.9% 🟢) 0.697s 15 1.00x
💻 Local Nitro 5.497s (-34.2% 🟢) 6.012s (-33.3% 🟢) 0.516s 5 4.20x
💻 Local Express 6.785s (-18.6% 🟢) 7.220s (-20.0% 🟢) 0.435s 5 5.19x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.138s (-9.5% 🟢) 2.008s (~) 0.870s 15 1.00x
🐘 Postgres Express 1.151s (-8.4% 🟢) 2.008s (~) 0.857s 15 1.01x
💻 Local Nitro 1.403s (-24.8% 🟢) 2.006s (-14.3% 🟢) 0.603s 15 1.23x
💻 Local Express 1.442s (-23.9% 🟢) 2.007s (-15.1% 🟢) 0.565s 15 1.27x
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.202s (-48.6% 🟢) 2.008s (-33.3% 🟢) 0.806s 15 1.00x
🐘 Postgres Express 1.235s (-47.2% 🟢) 2.008s (-33.3% 🟢) 0.773s 15 1.03x
💻 Local Nitro 1.933s (-36.9% 🟢) 2.391s (-38.5% 🟢) 0.458s 13 1.61x
💻 Local Express 2.067s (-34.0% 🟢) 2.470s (-34.3% 🟢) 0.404s 13 1.72x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.311s (-62.5% 🟢) 2.008s (-49.9% 🟢) 0.697s 15 1.00x
🐘 Postgres Nitro 1.315s (-62.2% 🟢) 2.007s (-49.9% 🟢) 0.692s 15 1.00x
💻 Local Nitro 5.610s (-38.7% 🟢) 6.214s (-38.0% 🟢) 0.604s 5 4.28x
💻 Local Express 5.988s (-32.0% 🟢) 6.214s (-33.0% 🟢) 0.226s 5 4.57x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.435s (-48.2% 🟢) 1.007s (-1.6%) 0.572s 60 1.00x
💻 Local Nitro 0.467s (-52.3% 🟢) 1.004s (-8.2% 🟢) 0.537s 60 1.08x
🐘 Postgres Nitro 0.474s (-42.2% 🟢) 1.006s (~) 0.532s 60 1.09x
💻 Local Express 0.482s (-51.1% 🟢) 1.004s (-6.7% 🟢) 0.522s 60 1.11x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.062s (-44.9% 🟢) 1.864s (-11.3% 🟢) 0.802s 49 1.00x
🐘 Postgres Express 1.095s (-44.6% 🟢) 1.807s (-20.0% 🟢) 0.713s 50 1.03x
💻 Local Nitro 1.166s (-61.6% 🟢) 2.005s (-46.6% 🟢) 0.840s 45 1.10x
💻 Local Express 1.300s (-56.9% 🟢) 2.028s (-43.4% 🟢) 0.728s 45 1.22x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.093s (-49.0% 🟢) 2.798s (-39.2% 🟢) 0.706s 43 1.00x
🐘 Postgres Express 2.107s (-47.2% 🟢) 2.561s (-41.4% 🟢) 0.454s 47 1.01x
💻 Local Nitro 2.637s (-71.6% 🟢) 3.007s (-70.0% 🟢) 0.370s 40 1.26x
💻 Local Express 3.044s (-67.0% 🟢) 3.524s (-64.8% 🟢) 0.480s 35 1.45x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.181s (-36.0% 🟢) 1.006s (~) 0.826s 60 1.00x
🐘 Postgres Nitro 0.190s (-32.8% 🟢) 1.006s (~) 0.816s 60 1.05x
💻 Local Nitro 0.408s (-32.5% 🟢) 1.004s (-1.7%) 0.596s 60 2.26x
💻 Local Express 0.546s (-2.6%) 1.006s (~) 0.460s 60 3.02x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.299s (-39.8% 🟢) 1.006s (~) 0.708s 90 1.00x
🐘 Postgres Express 0.299s (-41.3% 🟢) 1.007s (~) 0.707s 90 1.00x
💻 Local Nitro 2.179s (-14.2% 🟢) 2.766s (-8.1% 🟢) 0.587s 33 7.29x
💻 Local Express 2.324s (-7.5% 🟢) 2.978s (-1.0%) 0.653s 31 7.78x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.570s (-27.8% 🟢) 1.006s (~) 0.435s 120 1.00x
🐘 Postgres Express 0.613s (-25.1% 🟢) 1.006s (-1.1%) 0.393s 120 1.08x
💻 Local Nitro 9.982s (-10.8% 🟢) 10.444s (-10.5% 🟢) 0.462s 12 17.50x
💻 Local Express 10.423s (-6.9% 🟢) 11.128s (-6.8% 🟢) 0.705s 11 18.28x
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.135s (+431.2% 🔺) 2.005s (+99.6% 🔺) 0.012s (-2.4%) 2.019s (+98.2% 🔺) 0.884s 10 1.00x
🐘 Postgres Express 1.136s (+453.8% 🔺) 1.997s (+99.9% 🔺) 0.001s (-25.0% 🟢) 2.010s (+98.7% 🔺) 0.874s 10 1.00x
🐘 Postgres Nitro 1.141s (+456.6% 🔺) 2.002s (+100.3% 🔺) 0.001s (-13.3% 🟢) 2.011s (+98.9% 🔺) 0.870s 10 1.01x
💻 Local Express 1.179s (+492.0% 🔺) 2.006s (+99.8% 🔺) 0.014s (+13.2% 🔺) 2.024s (+98.8% 🔺) 0.845s 10 1.04x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.513s (+140.2% 🔺) 2.006s (+99.3% 🔺) 0.004s (+3.6%) 2.026s (+98.0% 🔺) 0.513s 30 1.00x
🐘 Postgres Nitro 1.549s (+148.2% 🔺) 2.008s (+99.5% 🔺) 0.003s (-17.0% 🟢) 2.024s (+98.0% 🔺) 0.475s 30 1.02x
💻 Local Express 1.637s (+116.2% 🔺) 2.013s (+95.6% 🔺) 0.010s (+8.4% 🔺) 2.025s (+94.8% 🔺) 0.388s 30 1.08x
💻 Local Nitro 1.699s (+102.6% 🔺) 2.010s (+98.6% 🔺) 0.010s (+9.0% 🔺) 2.201s (+97.3% 🔺) 0.502s 28 1.12x
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.638s (-34.1% 🟢) 1.016s (-18.6% 🟢) 0.000s (-59.3% 🟢) 1.024s (-18.6% 🟢) 0.385s 59 1.00x
🐘 Postgres Express 0.647s (-32.6% 🟢) 1.013s (-20.7% 🟢) 0.000s (+55.9% 🔺) 1.029s (-21.2% 🟢) 0.382s 59 1.01x
💻 Local Nitro 1.325s (+8.3% 🔺) 2.017s (~) 0.001s (+433.3% 🔺) 2.019s (~) 0.694s 30 2.07x
💻 Local Express 1.559s (+27.2% 🔺) 2.017s (~) 0.000s (-20.0% 🟢) 2.020s (~) 0.461s 30 2.44x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.275s (-28.0% 🟢) 2.093s (-3.9%) 0.000s (+Infinity% 🔺) 2.119s (-3.6%) 0.844s 29 1.00x
🐘 Postgres Nitro 1.322s (-26.2% 🟢) 2.070s (-3.3%) 0.000s (-3.4%) 2.106s (-3.1%) 0.784s 29 1.04x
💻 Local Nitro 3.016s (-11.0% 🟢) 3.736s (-7.4% 🟢) 0.001s (-0.7%) 3.738s (-7.4% 🟢) 0.722s 17 2.37x
💻 Local Express 3.159s (-8.9% 🟢) 4.031s (~) 0.001s (~) 4.034s (~) 0.875s 15 2.48x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 20/21
🐘 Postgres Express 13/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 19/21
Nitro 🐘 Postgres 14/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

Comment thread docs/content/docs/v4/api-reference/workflow-api/world/storage.mdx Outdated
Comment thread docs/content/docs/v4/api-reference/workflow-api/world/storage.mdx
Comment thread docs/content/docs/v4/api-reference/workflow/get-step-metadata.mdx Outdated
Comment thread docs/content/docs/v4/cookbook/advanced/child-workflows.mdx Outdated
Comment thread docs/content/docs/v4/cookbook/advanced/child-workflows.mdx Outdated
Comment thread docs/content/docs/v4/cookbook/advanced/child-workflows.mdx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands v4 and v5 documentation around idempotency, especially how duplicate workflow starts relate to start(), hooks, and application-level coordination.

Changes:

  • Adds run-idempotency guidance to foundations and cookbook pages.
  • Adds API reference callouts and related links for start(), hooks, getRun(), and workflow fetch().
  • Mirrors the updates across v4 and v5 docs.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
docs/content/docs/v5/foundations/idempotency.mdx Expands conceptual idempotency guidance.
docs/content/docs/v5/cookbook/integrations/sandbox.mdx Adds /start idempotency notes.
docs/content/docs/v5/cookbook/integrations/chat-sdk.mdx Adds first-message routing guidance.
docs/content/docs/v5/cookbook/integrations/ai-sdk.mdx Adds first-turn idempotency guidance.
docs/content/docs/v5/cookbook/index.mdx Updates idempotency summary text.
docs/content/docs/v5/cookbook/common-patterns/workflow-composition.mdx Adds child-run idempotency callout and links.
docs/content/docs/v5/cookbook/common-patterns/timeouts.mdx Links timeout side effects to idempotency.
docs/content/docs/v5/cookbook/common-patterns/scheduling.mdx Adds scheduled-run idempotency callout.
docs/content/docs/v5/cookbook/common-patterns/idempotency.mdx Reworks cookbook idempotency pattern.
docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx Adds agent-start idempotency note.
docs/content/docs/v5/cookbook/advanced/publishing-libraries.mdx Adds idempotency cross-link.
docs/content/docs/v5/cookbook/advanced/child-workflows.mdx Adds related start() link.
docs/content/docs/v5/api-reference/workflow/fetch.mdx Adds mutation/idempotency warning.
docs/content/docs/v5/api-reference/workflow/create-hook.mdx Adds hook-token idempotency callout.
docs/content/docs/v5/api-reference/workflow-api/world/storage.mdx Adds low-level hook lookup guidance.
docs/content/docs/v5/api-reference/workflow-api/start.mdx Documents that each start() call creates a run.
docs/content/docs/v5/api-reference/workflow-api/resume-hook.mdx Adds resume-or-start callout.
docs/content/docs/v5/api-reference/workflow-api/get-run.mdx Clarifies runId vs business-key lookup.
docs/content/docs/v5/api-reference/workflow-api/get-hook-by-token.mdx Adds idempotency-related hook lookup guidance.
docs/content/docs/v4/foundations/idempotency.mdx Mirrors v5 conceptual idempotency updates.
docs/content/docs/v4/cookbook/integrations/sandbox.mdx Mirrors sandbox idempotency notes.
docs/content/docs/v4/cookbook/integrations/chat-sdk.mdx Mirrors chat routing guidance.
docs/content/docs/v4/cookbook/integrations/ai-sdk.mdx Mirrors first-turn guidance.
docs/content/docs/v4/cookbook/index.mdx Mirrors idempotency summary update.
docs/content/docs/v4/cookbook/common-patterns/workflow-composition.mdx Mirrors composition callout and links.
docs/content/docs/v4/cookbook/common-patterns/timeouts.mdx Mirrors timeout/idempotency links.
docs/content/docs/v4/cookbook/common-patterns/scheduling.mdx Mirrors scheduled-run callout.
docs/content/docs/v4/cookbook/common-patterns/idempotency.mdx Mirrors cookbook idempotency rewrite.
docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx Mirrors agent-start note.
docs/content/docs/v4/cookbook/advanced/publishing-libraries.mdx Mirrors idempotency cross-link.
docs/content/docs/v4/cookbook/advanced/child-workflows.mdx Mirrors related start() link.
docs/content/docs/v4/api-reference/workflow/fetch.mdx Mirrors fetch warning.
docs/content/docs/v4/api-reference/workflow/create-hook.mdx Mirrors hook-token callout.
docs/content/docs/v4/api-reference/workflow-api/world/storage.mdx Mirrors low-level hook lookup guidance.
docs/content/docs/v4/api-reference/workflow-api/start.mdx Mirrors start() behavior note.
docs/content/docs/v4/api-reference/workflow-api/resume-hook.mdx Mirrors resume-or-start callout.
docs/content/docs/v4/api-reference/workflow-api/get-run.mdx Mirrors runId clarification.
docs/content/docs/v4/api-reference/workflow-api/get-hook-by-token.mdx Mirrors hook lookup guidance.
Comments suppressed due to low confidence (4)

docs/content/docs/v5/foundations/idempotency.mdx:142

  • This first request's confirmed payload is dropped when no hook exists yet: the code catches HookNotFoundError, starts the workflow, and returns without delivering confirmed to the newly created hook. If callers do not retry, processOrderWithConfirmation will wait for a payload that was already received by this route.
    docs/content/docs/v4/foundations/idempotency.mdx:142
  • This first request's confirmed payload is dropped when no hook exists yet: the code catches HookNotFoundError, starts the workflow, and returns without delivering confirmed to the newly created hook. If callers do not retry, processOrderWithConfirmation will wait for a payload that was already received by this route.
export async function POST(request: Request) {
  const { orderId, confirmed } = await request.json();
  const token = `order:${orderId}`;

  try {
    const hook = await resumeHook(token, { confirmed }); // [!code highlight]
    return Response.json({ runId: hook.runId, reused: true });
  } catch (error) {
    if (!HookNotFoundError.is(error)) throw error;
  }

  const run = await start(processOrderWithConfirmation, [orderId]); // [!code highlight]
  return Response.json({ runId: run.runId, reused: false });

docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx:131

  • Storing the runId only after start() returns is too late to prevent concurrent duplicate starts: two retried submissions can both start agent runs before either stores its runId. When duplicate run creation is unsafe, the route needs to atomically claim the conversation/request key before calling start() (or have a recovery path for the extra run).
<Callout type="info">
This route starts a new agent run for each request. If retried submissions should reconnect to the same agent run, store the returned `runId` behind an atomic conversation or request key before returning it. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
</Callout>

docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx:131

  • Storing the runId only after start() returns is too late to prevent concurrent duplicate starts: two retried submissions can both start agent runs before either stores its runId. When duplicate run creation is unsafe, the route needs to atomically claim the conversation/request key before calling start() (or have a recovery path for the extra run).
<Callout type="info">
This route starts a new agent run for each request. If retried submissions should reconnect to the same agent run, store the returned `runId` behind an atomic conversation or request key before returning it. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
</Callout>

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/content/docs/v5/cookbook/agent-patterns/durable-agent.mdx Outdated
Comment thread docs/content/docs/v4/cookbook/agent-patterns/durable-agent.mdx Outdated
Comment thread docs/content/docs/v5/foundations/idempotency.mdx Outdated
Comment thread docs/content/docs/v4/foundations/idempotency.mdx Outdated
Comment thread docs/content/docs/v5/foundations/idempotency.mdx Outdated
Comment thread docs/content/docs/v4/api-reference/workflow-api/resume-hook.mdx Outdated
Comment thread docs/content/docs/v5/cookbook/advanced/publishing-libraries.mdx Outdated
Comment thread docs/content/docs/v4/cookbook/advanced/publishing-libraries.mdx Outdated
Comment thread docs/content/docs/v5/api-reference/workflow-api/start.mdx Outdated
Comment thread docs/content/docs/v4/api-reference/workflow-api/start.mdx Outdated
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment-only review

Good docs PR. The split between step idempotency (use stepId from getStepMetadata()) and run idempotency (use a deterministic hook token) reads well, and the cross-links from start(), getRun(), getHookByToken(), resumeHook(), create-hook, and the cookbook integrations all point back to the same anchor consistently.

What I verified

  • Race-story claim is correct. Walked through the start() + createHook() timing: requests A and B both observe "no hook" → both call start() → both runs eventually reach createHook({ token: 'order:X' }) → first to register wins, second gets HookConflictError. The duplicate-run cleanup happens inside the workflow body. ✅
  • using _idempotency = createHook(...) pattern matches the existing usage in migrating-from-aws-step-functions.mdx, migrating-from-temporal.mdx, migrating-from-trigger-dev.mdx, and migrating-from-inngest.mdx. Consistent.
  • No wording regressions like the "Vercelism" issue in #2010. The new wording is consistent with the rest of the docs.
  • v4/v5 content identical — verified with diff. Reasonable since idempotency semantics didn't change between versions.
  • All cross-links resolve — the #run-idempotency anchor exists in both v4 and v5 idempotency pages.

Inline comments

Two non-blocking observations:

  1. Missing changeset (same convention as #2010).
  2. Minor wording suggestion on the race story callout to be slightly more concrete about what's racing.

Otherwise looks good. Once the changeset is added I'd approve. Posting as comment-only to keep the door open for the wording suggestion.

Comment thread docs/content/docs/v5/foundations/idempotency.mdx
Comment thread docs/content/docs/v5/foundations/idempotency.mdx
Signed-off-by: Nathan Rajlich <n@n8.io>
@TooTallNate
Copy link
Copy Markdown
Member

Pushed an empty changeset directly to the branch (commit 642c898) to unblock the changeset check — figured that was easier than asking you to round-trip on a 2-line file. Otherwise still LGTM from my review above.

Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changeset is in (pushed 642c898 directly). Approving — the wording suggestion I left earlier is non-blocking.

@pranaygp
Copy link
Copy Markdown
Contributor Author

Moving this back to draft while we sort out the right run-idempotency API/pattern.

One issue with the current hook-token approach: HookConflictError is only observed when the returned hook is awaited or iterated. That means using createHook({ token }) as an idempotency claim forces the workflow to wait for hook data, even for workflows that only want to reserve a deterministic token and never expect any data to be sent. We want a run-idempotency pattern that supports this “claim only” case without requiring an artificial resume payload or blocking on hook data.

So before this docs PR is ready, we should decide whether Workflow needs a first-class claim/registration primitive, a hook readiness/claim API, or changed createHook() semantics that let workflows observe conflicts without waiting for hook payloads.

@pranaygp
Copy link
Copy Markdown
Contributor Author

Opened the prerequisite runtime/API PR here: #2015

Once that lands, this docs PR should update the idempotency examples to use the new hook.ready getter:

using hook = createHook({ token });
await hook.ready;
// duplicate-sensitive work can run after the hook token is registered

That avoids requiring consumers to wait for or send hook payload data just to claim the token for run idempotency. In other words, the docs/code examples here should use .ready for the idempotency claim, and only await/resume the hook itself when they actually need payload data.

pranaygp and others added 4 commits May 18, 2026 16:59
…un-idempotency

* origin/main:
  Add workflow versioning docs (#2010)
  Hide flaky worlds indicators (#2000)

# Conflicts:
#	docs/content/docs/v4/cookbook/common-patterns/workflow-composition.mdx
#	docs/content/docs/v5/cookbook/common-patterns/workflow-composition.mdx
…un-idempotency

* origin/main:
  Expose conflicting run id on hook conflicts (#2012)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants