* origin/main:
test: regression coverage for hook.resume() from isolated route bundles (o2flow beta.26 incident) (#3001)
docs(agents): note lint/format/typecheck are advisory, not blocking (#2886)
Retry transient connection timeouts (#3013)
fix(world-vercel): append caller User-Agent products instead of discarding them (#2998)
[ci] Enable NestJS e2e-vercel-prod and add to docs as "experimental" (#3011)
[ci] Benchmark comment: Best column + best/p75/p99 deltas (drop Avg/P10) (#3005)
docs: fall back to first child page for sidebar folders without an index (#3009)
[nest] Fix NestJS Vercel build output (#2988)
Avoid resolving run data for background steps (#2993)
chore(docs): update @vercel/geistdocs to 1.14.0 (#3002)
fix(docs): add version-switcher fallback redirects for pages missing in one version (#3003)
ci: update opencode to 1.18.4 and switch backport AI model to claude-fable-5 (#3006)
fix(core): batch stream writes via writeMulti (#2995)
Owner feedback on the opt-in fast path: stream-based `await run.returnValue`
should be on by default and work on every World, since all worlds already
implement streams.
- `WORKFLOW_RETURN_VALUE_STREAM` is now a default-on emergency kill switch
(`=0`/`false` restores the pure fixed 1s poll), mirroring `WORKFLOW_TURBO`
and `WORKFLOW_INLINE_OWNERSHIP`.
- Drop the `returnValueSignalStream` World capability gate. Verified the
marker-write + close / `startIndex: 0` catch-up contract the waiter relies on
works out of the box on world-local (filesystem) and world-postgres
(NOTIFY + durable rows); world-vercel relies on workflow-server's durable
chunk replay to late readers (documented). Nothing needed fixing.
- Keep the 5s fallback poll (`WORKFLOW_RETURN_VALUE_FALLBACK_POLL_MS`) as the
internal never-hang backstop.
- Tests: kill-switch-off is byte-identical to the legacy poll; per-world
catch-up tests for local (in core) and postgres (testcontainers); drop the
missing-capability tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: regression coverage for hook.resume() from isolated route bundles (o2flow beta.26 incident)
Reproduces the o2flow v5 upgrade failure (workflow@5.0.0-beta.26, fixed by
#2752 in beta.28): a plain API route importing defineHook() from the root
`workflow` entry and calling .resume() failed with Turbopack's
"Cannot find module as expression is too dynamic" stub, because the world
registration was tree-shaken out of the route bundle and getWorldLazy()'s
dynamic-import fallback got stubbed.
The bug only manifests when a route bundle loads in isolation (a Vercel
lambda): local `next dev`/`next start` evaluates next.config.ts, whose
workflow/next import chain registers the world process-wide and masks it —
which is why no existing server-driven suite caught it.
- route-bundle-isolation.test.ts: production Turbopack build of the
nextjs-turbopack workbench, then loads ONLY the compiled route bundle in a
bare Node subprocess (cold-lambda simulation) and invokes its POST handler.
Fails with the exact incident error on regressed code; passes on main.
Wired into the build-error-messages CI job.
- e2e: plainModuleDoneHook round-trip through a plain API route on the two
Next workbenches (deployed matrix covers real lambda isolation).
- Workbench fixtures mirroring o2flow: a directive-less defineHook module
shared by a workflow (create) and a plain route (resume). The webpack
workbench gets a real route file because `next dev` (webpack) does not
serve directory-symlinked app routes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* test: authenticate plain hook resume request
* test: address review — marker-based harness output parsing, changeset summary
- route-bundle-isolation: prefix the harness result line with a unique
marker and locate it explicitly instead of JSON.parse()ing the last
stdout line, so stray logging from the route bundle or the world can't
break parsing; failures now include the full subprocess stdout.
- changeset: add a human-readable summary to the (release-less) changeset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
---------
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Karthik Kalyanaraman <karthik.kalyanaraman@vercel.com>
Co-authored-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>
`await run.returnValue` polls the run record on a fixed ~1s interval, adding
up to a second of quantization latency for a run that finishes mid-interval
(production trace 60cc034ba74b04e0fa2f70aa302a027f).
Add an opt-in fast path: when `WORKFLOW_RETURN_VALUE_STREAM` is on and the
World declares the new `returnValueSignalStream` capability, the poll loop
waits on a run-scoped system stream (`strm_…_system_return`) instead of the
fixed sleep. Every terminal transition in the live flow runtime — plus both
cancellation entry points — writes a tiny marker to that stream and closes it,
waking the waiter within a stream round-trip. The stream is a signal only: the
waiter always re-reads the authoritative run via `runs.get`, and a slow
fallback poll backstops any missed signal (crash between the terminal event
write and the stream write, transient stream failures, pre-feature runs).
Flag off, or a World without the capability, is byte-identical to the previous
fixed-poll behavior. world-vercel declares the capability.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: make /worlds the canonical home for World docs
The world pages (Local/Postgres/Vercel) and Building a World were
duplicated inside the v4 and v5 docs trees while /worlds/[id] rendered
the v4 copy — hiding v5-only content like multi-region and leaving two
diverging sources of truth.
- Move world docs to an unversioned docs/content/worlds/ collection
(based on the v5 copies, with inline 4.x callouts for factory naming
and 5.x-only env vars), rendered at /worlds/*
- Add /worlds/building-a-world; flatten the docs Deploying section to a
single intro page and drop its Rocket icon
- Point every link, frontmatter ref, and worlds-manifest docs field at
/worlds/*; add redirects for the removed v5 and building-a-world URLs
- Keep world docs on agent-facing surfaces: search, llms.txt,
sitemap.md/.xml, and .md exports now serve the worlds collection
- Extend the docs link linter to validate worlds pages (with heading
anchors) and their outgoing links
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* docs: version the world docs like the docs trees (v4/v5 switcher)
Instead of a single unversioned copy, world docs now follow the same
versioning strategy as the docs pages: content/worlds/v4 is served at
/worlds/* (current) and content/worlds/v5 at /v5/worlds/*, restoring the
original per-version content. Each world detail page (and Building a
World) renders the docs version switcher — the worlds listing page has
no natural home for it, so it lives on the world pages themselves.
- Render-time href rewriting on v5 pages now covers /worlds/... links
(shared rewriteHrefForVersion helper, also used by the v5 docs and
cookbook routes), and the markdown-export rewrite does the same
- v5 world pages are noindexed with a canonical to /worlds/<id>;
community worlds stay unversioned (/v5/worlds/<id> redirects)
- /v5/docs/deploying/world/* redirects now land on /v5/worlds/*;
/v5/worlds and /v5/worlds/compare redirect to the unversioned pages
- Link linter models the versioned worlds URL spaces (v5 pages resolve
/worlds hrefs against the v5 collection); sitemap.md and the .md
export routes cover /v5/worlds/*
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* docs: fix v4 multi-region anchor and tighten version-prefix matching
Address PR review:
- The v4 Deploying page linked /worlds/vercel#multi-region, but the
Multi-region section only exists on the v5 world page; use the
explicit cross-version /v5/worlds/vercel#multi-region link (this was
the Docs Links CI failure)
- rewriteHrefForVersion now uses the boundary-checked hasPathPrefix
(shared leaf module lib/geistdocs/path-prefix.ts, also used by
source.ts) instead of bare startsWith
- buildVersionUrl's shared-route fast path is segment-based rather than
substring includes()
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
---------
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Hook timeline bars were amber/yellow via RESOURCE_COLORS; passive
spans should be gray to match the event list icons and minimap.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Give Metadata Token and Hook ID copy + truncation
Add token to the copyable metadata attributes set and constrain
copyable key-value rows so MiddleTruncate can shrink long IDs.
* Remove AttributePanel copy unit tests
The Metadata Token/Hook ID copy change is small enough that the
dedicated panel render tests are unnecessary.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(nitro): support React Router Vite builds
* refactor(nitro): simplify React Router cleanup
* docs(react-router): specify cleanup version
* fix(nitro): close temporary Vite servers
The recoverActiveRuns factory option had no environment variable, so
disabling startup re-enqueueing of pending/running runs required a custom
world module via WORKFLOW_TARGET_WORLD. Wire an env fallback
(0/false disables, 1/true enables, explicit factory option wins) and
document it in the worlds configuration reference and local world guide.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): dedupe @opentelemetry/api to a single workspace instance
The lockfile resolved both 1.9.0 and 1.9.1, so the copy that registers
the tracer provider (via @vercel/otel in the app) and the copy a package
imports could differ. The API's global-registration version check rejects
a consumer newer than the registered copy and silently hands back a noop
tracer — which is why world-vercel's spans (workflow.stream.write/
chunk_rtt, read.connect, its http spans) never reached Datadog from
deployed apps while core's spans flowed in the same process. Root-caused
via the DEBUG=workflow:* run on #2900: import succeeds, no warn, spans
dropped.
Pin a single version via a workspace override so every bundle shares one
API instance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* debug: one-shot OTEL runtime diagnostic in core + world-vercel; DEBUG on turbopack workbench
The dedupe alone did not restore world-vercel span emission (verified on
this PR's own preview: stream traffic flowed, zero workflow.stream.write
spans). Under DEBUG=workflow:*, both packages now log once how their
module instance of @opentelemetry/api sees the world — global
registration version, provider/delegate/tracer/probe constructor names,
and whether a probe span is recording. Diffing the core line (spans work)
against the world-vercel line (spans dropped) in one deployment's logs
pinpoints the divergence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* debug: log span identity for named world-vercel spans; namespace otel probes per package
Diag round 1 showed world-vercel's tracer records and instrumentedFetch
handles the stream PUTs, yet the named spans are unfindable in the
backend. Round 2: log traceId/spanId/isRecording for every named
instrumentedFetch span under DEBUG so export can be checked for a
specific span id, and split the probe span names (.core /
.world_vercel) so per-package export is attributable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* telemetry: emit stream RPC latencies from core (chunk_rtt, connect_ms, close span)
world-vercel's instrumentedFetch spans never export from deployed apps
(root cause still open — see PR discussion), so the operationally
needed client-side latency signals move one layer up to core, whose
spans are proven to export:
- workflow.stream.write.chunk_rtt on the workflow.stream.flush span:
the World write RPC duration, network included (same attribute key as
world-vercel's per-request span so queries are layer-agnostic).
- workflow.stream.read.connect_ms on the workflow.stream.read span:
the world.streams.get await (read dispatch -> stream handle).
- new workflow.stream.close span: the close RPC round trip.
Bonus: measured at the World interface, these cover world-local and
world-postgres too, not just Vercel deployments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* telemetry: emit read-completion span (total duration, chunks, bytes)
Completes the read-side picture: workflow.stream.read.complete is
back-dated to the read dispatch so its duration is the total read, with
chunk/byte counts for throughput. Cancelled reads emit nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: drop DEBUG from turbopack workbench; tighten changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* telemetry: cover createReconnectingFramedStream in read telemetry
Ordinary serialized streams read through createReconnectingFramedStream
(which calls world.streams.get directly), so connect_ms / ttfc /
read.complete never fired for that path — only WorkflowServerReadableStream
was instrumented. Wire the same helpers into the framed reader: first-
connect duration, first-frame TTFC, and completion totals — plus
workflow.stream.read.reconnects, which only this path can know.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- analytics: attribute the serving path to the Vercel observability data
pipeline instead of naming the ClickHouse store, drop the Vercel-only
"Plan-bounded lookback" bullet, and mark the pageInfo lookback ceiling
and upgradeAvailable comments as Vercel-specific.
- sidebar: folder rows without an index page render as <button>s, which
don't stretch to the row width like the <a> folder links, leaving their
ms-auto chevron hugging the label. Stretch those triggers to full width
so every chevron sits at the end of the row.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Audited every vercel.com link in docs/content (20 unique URLs,
HTTP-validated including anchor fragments):
- project-configuration#regions (2x): the #regions anchor no longer
exists on that page — content moved to the vercel-json subpage; now
links project-configuration/vercel-json#regions
- gateway/api-reference/overview (2x): hard 404; the AI Gateway docs
restructured — 'get an API key' context now points at
ai-gateway/authentication
- observability/otel-overview (1x): redirects to
tracing/instrumentation; link the final URL
- docs/workflow and docs/workflow/python (8x): redirect to the plural
docs/workflows paths; link the final URLs (also drops a redundant
?language=py param that the redirect discards)
All other links (queues, queues/pricing + anchors, plans/hobby,
limits, regions, sandbox, workflows/pricing#storage-retention,
cli/project-linking, audit-log, home, help, blog) verified 200 with
live anchors.