* Redrive on transient workflow-server transport failures instead of failing the run
A firewall in front of workflow-server shedding load with sustained 429/503
makes undici's shared RetryAgent exhaust its retries and throw
UND_ERR_REQ_RETRY. That raw error was rethrown unwrapped, so it was
classified as USER_ERROR and the replay terminal branch wrote run_failed —
permanently failing a run on a transient blip (or, in an outage, falling back
to the ~5min queue visibility-timeout redrive).
- world-vercel: map exhausted-retry / socket / connect / DNS / timeout
failures to a typed WorkflowWorldError (code TRANSPORT/TIMEOUT) by walking
the fetch() cause chain.
- core: add isRetryableWorldError (429 / 5xx / TRANSPORT / TIMEOUT) and
rethrow such errors from the replay terminal branch so the queue redrives
quickly (1s->60s backoff) instead of failing the run. Reuse it in start()
and step_started handling.
- world-vercel: surface the Vercel firewall x-vercel-mitigated
(challenge/deny) header alongside x-vercel-id in error diagnostics and logs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review: back off + cap on every retry path; fix mock; refine scope
Revises the transport-error handling per PR review (VaguelySerious,
karthikscale3).
Blocking fix — step_started no longer self-enqueues a throttled defer for
transient world errors. Returning `{ type: 'throttled', timeoutSeconds: 1 }`
acked the delivery and enqueued a fresh message, resetting the delivery count
so the path never backed off and never reached MAX_QUEUE_DELIVERIES — an
unbounded flat-1s loop if step_started kept failing. It now throws, so the
error flows through the replay loop's retryable-world-error rethrow and earns
both the delivery-count backoff and the max-delivery cap. Throwing is safe on
step_started (the body hasn't run; a write that landed dedupes to skipped).
Also in this revision:
- Backoff that lasts: raise the queue handler-error retry ceiling 60s -> 900s.
VQS clamps each redelivery to its 900s SQS limit and adds its own post-32
exponential, so ramping our base toward 900s stretches survival from ~3.7h to
most of the 24h message-visibility window. Corrected the stale
MAX_QUEUE_DELIVERIES comment to match the real VQS schedule.
- Stop amplifying firewall challenges: the undici RetryAgent no longer retries
429 in-process (a challenge is a 429 the client can't solve). 429s surface
immediately as ThrottleError carrying x-vercel-mitigated / x-vercel-id, so
the diagnostic header now reaches us for the challenge case too.
- Track world faults as WORLD_CONTRACT_ERROR (not USER_ERROR) in
classifyRunError so an outage isn't attributed to user code.
- Fix queue.test.ts mock that `biome check --write` had rewritten from a
newable `function` into an arrow (broke `new QueueClient`); pin with a
biome-ignore.
All Vercel-specific logic stays in @workflow/world-vercel; @workflow/core
operates only on the generic WorkflowWorldError abstraction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Update .changeset/transport-error-redrive.md
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* Trim changeset to a single sentence per review
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Route firewall-challenge 429s to the retryable transport path, not ThrottleError
A 429 carrying `x-vercel-mitigated: challenge` is a firewall challenge our
server-to-server client cannot solve, so it recurs for the life of the
incident. Mapping it to `ThrottleError` meant the `step_started` write deferred
it as `{ type: 'throttled' }`, which self-enqueues a FRESH queue message and
resets the delivery count — so it never backed off past `retryAfter` and never
reached `MAX_QUEUE_DELIVERIES`, hot-looping against an already-overloaded
firewall (the exact amplification this PR set out to remove, and contrary to
the "step_started can't loop unbounded" invariant, which only held for 5xx).
Map a challenge to a retryable transport `WorkflowWorldError` (`code:
'TRANSPORT'`) in both the v3 `makeRequest` and v4 `throwForErrorResponse`
(the hot event-write path) error mappings, via a shared `isFirewallChallenge429`
helper. It then propagates through the V1/V2 step paths and the replay loop's
retryable-world-error rethrow, earning the delivery-count backoff AND the
delivery cap. A genuine application-level 429 (no `challenge` mitigation) stays
a `ThrottleError` and keeps its `Retry-After`-paced defer.
Also correct the survival-window comments: with the 900s ceiling,
MAX_QUEUE_DELIVERIES=48 spans ~9-10h (~35,000s), not "the better part of 24h";
reaching 24h would need a higher delivery cap, not a higher per-hop ceiling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
* fix(world-vercel): retry idempotent event POSTs in-process to avoid step re-execution
undici's RetryAgent never retries a POST, so a transient transport blip (UND_ERR_REQ_RETRY, ECONNRESET, socket/headers timeout, transient 5xx) when committing a step's terminal event bubbles out, the queue redelivers, and the step's user code re-executes with attempt++ even though it already ran to completion.
workflow-server makes these writes idempotent in outcome: entity handlers run before the event-log row is inserted and state transitions are conditional writes excluding terminal states, so a retry whose original landed throws before any row is written and surfaces as a 409 the SDK already handles. This adds a bounded in-process retry (new event-retry.ts) gated by a validated per-event EVENT_RETRY_ELIGIBILITY map, excluding step_started (double-increments attempt), step_retrying (appends a duplicate row), and hook_received (no server guard).
Complements #2666, which routes completion-persistence failures to queue redelivery instead of recording them as user failures; this avoids the redelivery (and re-execution) for transient blips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(world-vercel): address review on event-POST retry
- Don't retry external cancellation: drop AbortError from the transient set (keep self-timeout TimeoutError), so a caller-requested abort isn't re-issued or stalled by the backoff budget.
- Add DEBUG-gated logging on each retry and on retry exhaustion so an in-process retry vs. a fall-through to queue redelivery is distinguishable in logs.
- Clarify docs: a landed retry surfaces as 409 for most types, but run_started/attr_set return 200 success (not a 409).
- Add createWorkflowRunEvent integration tests (events-retry.test.ts): eventType is threaded into the retry wrapper, and the 404->HookNotFoundError mapping still fires after the retry loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
* Add selection-driven span-detail primitives
Extract the run/step/hook/sleep fetch+hydrate core out of useWorkflowResourceData
into a plain async fetchSpanDetailResource (no React state), and add a
selection-driven state machine in web-shared:
- deriveSpanDetailView / resourceNeedsFetchedDetail: pure view-model deriver
whose status (idle/loading/ready/error) is a function of (selection, fetched
detail), so it can never lag the selection.
- useSelectedSpanDetail: fetches a selected span's detail directly with a
request-token to drop stale/out-of-order responses.
* Drive trace detail panel from the span-detail state machine
Replace the cross-package selection round-trip (EntityDetailPanel useEffect ->
onSpanSelect -> page spanSelection state -> useWorkflowResourceData -> context)
with a single injected fetchSpanDetail capability:
- EntityDetailPanel consumes useSelectedSpanDetail; its loading state now stays
in phase with the selected span, so Input/Output no longer vanish and pop back
in while navigating.
- SidebarDataContext drops spanDetailData/Loading/Error + onSpanSelect for a
single fetchSpanDetail; RunDetailView injects it and drops the duplicate
spanSelection state.
- WorkflowTraceViewer / RunTraceView take fetchSpanDetail too.
* Test span-detail view-model transitions; add changeset
Cover deriveSpanDetailView (idle/loading/ready/error, stale-detail rejection,
hooks ready inline) and resourceNeedsFetchedDetail.
* Trim redundant/narrative comments in span-detail state machine
Comment-only cleanup: drop PR-narration and cross-file duplication from the
deriveSpanDetailView / useSelectedSpanDetail / EntityDetailPanel / fetchSpanDetail
doc comments, keeping the non-obvious intent (request-token, error scoping,
decrypt closure).
* delete pointless coments
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
- Add deprecation banners (with migration-guide link) to the DurableAgent and
WorkflowChatTransport API references in v4 and v5; keep the full API surface intact
- Bring v4 headline guides to parity with v5's WorkflowAgent migration (ai/index,
foundations/streaming, the cookbook recipe + index)
- Convert standard agent examples (defining-tools, message-queueing) to WorkflowAgent
and reframe the streamText-vs comparison page
- Banner + repoint the deep recipes that stream custom UIMessageChunk data parts
(chat-session-modeling, human-in-the-loop, agent-cancellation, serializable-steps) —
that pattern doesn't map to WorkflowAgent's ModelCallStreamPart model, so their
legacy DurableAgent examples are kept behind a clear deprecation banner
- Point all WorkflowChatTransport examples at the @ai-sdk/workflow 1:1 port
- Rename the cookbook agent-patterns recipe to WorkflowAgent
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Move cn to its own lib/cn module that teaches tailwind-merge about the
design-system text-heading/text-label/text-copy/text-button/material
utilities, and import it directly from lib/cn.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Propagate trace context to vercel-workflow.com in workbench instrumentation
@vercel/otel only propagates W3C trace context to Vercel deployment URLs
by default, so outgoing requests to the workflow-server
(vercel-workflow.com) got a client span with no `traceparent` header —
breaking the APM trace link to workflow-server's spans. Add
`instrumentationConfig.fetch.propagateContextUrls` for the workflow-server
domain in every workbench that uses @vercel/otel: example,
nextjs-turbopack, nextjs-webpack, and sveltekit. The Next.js and SvelteKit
apps already declared @vercel/otel but weren't registering it at all; they
now do.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Also propagate trace context to the Vercel Queue Service (vercel-queue.com)
The workflow-server queue path (@vercel/queue) sends to regional
vercel-queue.com subdomains (e.g. iad1.vercel-queue.com) when not using the
queues proxy, which were missing a `traceparent` header for the same reason
as vercel-workflow.com. Add `/vercel-queue\.com/` to propagateContextUrls in
all four workbench instrumentation configs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Use text-gray-900 for trace shortcut helper text
Co-authored-by: Mitul Shah <mitulxshah@gmail.com>
* Add changeset for trace shortcut helper text color
Co-authored-by: Mitul Shah <mitulxshah@gmail.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(web-shared): stop data inspector duplicating expanded objects
Expanded objects/arrays now render bracket delimiters ({ … } / [ … ])
instead of repeating the inline preview alongside the child tree.
Co-authored-by: Cursor <cursoragent@cursor.com>
* nice
* fix: sync lockfile after dropping react-inspector
The react-inspector removal landed in package.json but the lockfile was
reverted during cleanup, breaking frozen-lockfile installs in CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): keep data inspector colors theme-aware
Drop the dark-mode color overrides (and the data-theme/useDarkMode
wiring) and rely on the theme-aware --ds-* tokens, matching front:
strings stay green in both light and dark instead of turning blue.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(web-shared): add data inspector tests; tidy comments
Cover collapseRefs ref/typed-array/Map/Set handling and the rendered
tree (keys, value colors, brackets, commas, collapse/expand, empties,
dates, class/Map/Set prefixes) via jsdom + testing-library.
Co-authored-by: Cursor <cursoragent@cursor.com>
* revert(web-shared): drop data inspector tests
Remove the test suite and its jsdom/@testing-library devDependencies to
avoid adding new packages. Keeps the data inspector code unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(web-shared): extract data inspector styles to a sibling module
Move the class-name map and CSS string out of the component into
data-inspector.styles.ts for readability. Still injected via the
hoistable <style>; no behavior or dependency change.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): restore ARIA tree semantics for the data inspector
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web-shared): render RegExp values as /source/flags
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(web-shared): add unit tests for CopyableDataBlock JSON viewer (#2584)
Export serializeForClipboard and cover its clipboard formatting (strings, primitives, pretty-printed JSON, circular/BigInt fallbacks) plus CopyableDataBlock/EncryptedDataBlock rendering.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Swap the text-based eve placeholder for the real eve wordmark (hard-copied
SVG from @vercel/geistcn-assets, themed via currentColor) and drop Streamdown
so AI Elements is last.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
* Add Platformatic World to worlds-manifest.json
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
* ci fixup
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
* ci: pin platformatic world image to 0.8.1 and harden community-world runner
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
* platforamtic-world version
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
* ci: wire generic docker service-type into community benchmark workflow
The shared community-worlds matrix now emits service-type "docker" for any
world with non-builtin or multiple services (e.g. Platformatic, which needs
postgres + the platformatic/workflow image). tests.yml's e2e-community path
already handles it, but benchmarks.yml's benchmark-community path
(label-gated, non-blocking) did not — so a "community-benchmarks" run would
start no services and fail.
Mirror the e2e "Start Docker services" step, package-version pin, and docker
cleanup into benchmark-community-world.yml, and pass `services`/`version`
through from benchmarks.yml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Section index card grids (e.g. foundations) were hand-written and drifted
from the sidebar (meta.json) and the actual pages. Make them derive from
the fumadocs page tree (single source of truth) and add CI lint so the
card grid and navigation can't fall out of sync again.
- resolveSectionChildren + <AutoCards/>, bound in both v4 and v5 docs
routes (correct /docs vs /v5/docs URL spaces)
- getLLMText expands <AutoCards/> so llms.txt/.md/copy-page keep child links
- manualCards frontmatter opt-out for curated pages (source.config.ts)
- checkSectionCards (card<->nav completeness) + checkMetaEntriesResolve
(dangling meta entries) in scripts/lint.ts
- convert foundations + errors (drift fixes) and v5 observability to AutoCards
- mark deploying + ai as manualCards (intentionally curated)
- remove dangling meta entries: v4 cancellation (x2), root introduction
(x2), v4/internal serializable-abort-controller
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>