Skip to content

fix(eve): self-hosted workflow queue handler + world version-compat guard#308

Merged
AndrewBarba merged 2 commits into
mainfrom
pgp/eve-self-host-workflow-runtime
Jun 25, 2026
Merged

fix(eve): self-hosted workflow queue handler + world version-compat guard#308
AndrewBarba merged 2 commits into
mainfrom
pgp/eve-self-host-workflow-runtime

Conversation

@pranaygp

Copy link
Copy Markdown
Contributor

Two related fixes for the self-hosted Postgres workflow-world story. Pairs with the sibling docs PR on branch pgp/eve-self-host-workflow-docs.

FIX 1 — eve start registers the workflow queue handler for self-hosted (non-Vercel) production

Bug: With a custom/local workflow world configured (e.g. @workflow/world-postgres) and the documented production path eve build && eve start, the direct queue→bundle handler binding was never registered. Jobs the world dispatched to /.well-known/workflow/v1/flow came back 400 {"error":"Unhandled queue"} and runs sat in pending forever. The only workaround was eve dev --no-ui, which is semantically wrong for production.

Root cause: In packages/eve/src/internal/nitro/host/configure-nitro-routes.ts the directHandlerEntries were gated on nitro.options.dev. The HTTP flow route is registered in production, but its queue→bundle binding (generated only when directHandlerEntries is non-empty) was not. The real axis is not dev-vs-prod; it is who drives the queue: the local/configured world drives it in-process in both eve dev and self-hosted eve start, whereas Vercel dispatches through its managed queue trigger over HTTP.

Fix: Register the binding whenever the local world drives the queue — dev, OR a non-Vercel build (!isVercelBuildEnvironment()) with a configured custom world (manifest.config.experimental.workflow.world set) — and never for Vercel-managed deploys. isVercelBuildEnvironment() is now exported from packages/eve/src/internal/application/paths.ts. The Vercel path is preserved exactly (binding still absent when VERCEL is set at build).

Final gate condition:

const hasConfiguredWorkflowWorld =
  preparedHost.compileResult.manifest.config.experimental?.workflow?.world !== undefined;
const localWorldDrivesQueue =
  nitro.options.dev || (!isVercelBuildEnvironment() && hasConfiguredWorkflowWorld);

The added custom-world clause keeps non-custom-world production builds byte-for-byte identical to today (no binding); only custom-world self-hosted prod — the bug — changes.

End-to-end wiring question (resolved)

I traced @workflow/world-local's queue dispatch (queue.js): if a direct handler is registered for the queue prefix it calls it in-process and short-circuits the HTTP path entirely; only when no direct handler exists does it fall back to fetch(${baseUrl}/.well-known/workflow/v1/...) where baseUrl comes from WORKFLOW_LOCAL_BASE_URL/PORT. So registering the binding (FIX 1) is the complete fix for self-hosted runs and does not require the installWorkflowLocalQueueEnvironment env wiring used by eve dev — the world never reaches the HTTP fallback. As a belt-and-suspenders, startProductionServer already sets PORT on the spawned child, so even the fallback would resolve http://localhost:${PORT}. Full end-to-end validation against a real Postgres world remains out of scope for unit tests (no Postgres in CI); the in-process dispatch path is exercised by the existing/added unit tests.

Tests (FIX 1)

  • Retargeted does not register direct workflow queue handlers in production builds...for Vercel production builds (stubs VERCEL=1 via vi.stubEnv).
  • Added registers direct workflow queue handlers for self-hosted production builds with a configured world.
  • Added does not register direct workflow queue handlers for self-hosted production builds without a configured world.
  • beforeEach now vi.unstubAllEnvs() to keep env from leaking; paths.js is mocked to a real isVercelBuildEnvironment that reads process.env.VERCEL, so vi.stubEnv still drives behavior while avoiding the heavy import graph.

FIX 2 — boot-time workflow world version-compat guard

Trap: pnpm add @workflow/world-postgres resolves to npm latest (4.x), incompatible with the @workflow/* 5.0.0-beta line eve bundles (@workflow/core@5.0.0-beta.24). The mismatch surfaced as a cryptic ZodError: invalid_union deep in workflow replay.

Fix: A simple, low-risk eve-side guard that fails loud and early. New pure, exported, doc-commented helper assertWorkflowWorldCompatibility (packages/eve/src/internal/workflow/world-compatibility.ts) takes already-parsed JSON and throws only on a definite major / prerelease-line mismatch; it no-ops when the world declares no @workflow/* dep or the range is unparseable (avoids false-positive boot failures). The expected line comes from a single source of truth — eve's own package.json via new resolveExpectedWorkflowVersion() in packages/eve/src/internal/application/package.ts — so no version is hardcoded. The world package name is threaded from the generated bootstrap (compiled-artifacts.ts) into installConfiguredWorkflowWorld({ module, packageName }), which reads the world's package.json via createRequire(import.meta.url).resolve(...) and calls the pure helper. No new runtime dependency (no semver lib; a tiny major/prerelease-line parse). The deeper, fully version-aware fix is being proposed upstream in @workflow/core via a separate issue.

Example thrown message:
Configured Workflow world "@workflow/world-postgres" targets @workflow/core 4.x, but this eve release requires the @workflow/core 5.0.0-beta line. Install a matching world, e.g. \pnpm add @workflow/world-postgres@5.0.0-beta.24`.`

Tests (FIX 2)

  • world-compatibility.test.ts (pure helper): older major → throws actionable message (asserts name + line + install command); same prerelease line / caret range → passes; falls back to @workflow/world and reads peerDependencies; no @workflow/* dep / unparseable range / unparseable expected → no-op.
  • package.test.ts: resolveExpectedWorkflowVersion reads eve's @workflow/core line.
  • compiled-artifacts.test.ts: bootstrap body assertion updated to include packageName.

Files changed

  • packages/eve/src/internal/nitro/host/configure-nitro-routes.ts — new gate; import isVercelBuildEnvironment.
  • packages/eve/src/internal/application/paths.ts — export isVercelBuildEnvironment (+ doc).
  • packages/eve/src/internal/nitro/host/configure-nitro-routes.test.ts — retargeted/added tests; env hygiene; mock paths.js.
  • packages/eve/src/internal/workflow/world-compatibility.ts — new pure helper.
  • packages/eve/src/internal/workflow/world-compatibility.test.ts — new helper tests.
  • packages/eve/src/internal/workflow/configure-world.ts — boot-time compat check; thread packageName.
  • packages/eve/src/internal/application/package.ts — new resolveExpectedWorkflowVersion.
  • packages/eve/src/internal/application/package.test.ts — test for the above.
  • packages/eve/src/internal/application/compiled-artifacts.ts — pass packageName to bootstrap call.
  • packages/eve/src/internal/application/compiled-artifacts.test.ts — updated bootstrap assertion.
  • .changeset/self-host-workflow-runtime.md — patch changeset.

Validation

  • pnpm install --frozen-lockfile
  • pnpm --filter eve typecheck
  • oxlint packages/eve/src ✓ (clean) · pnpm guard:invariants
  • Unit: touched files — 89 passed (9 files). Integration/scenario/e2e left to CI.

🤖 Generated with Claude Code

pranaygp and others added 2 commits June 25, 2026 13:43
`eve start` previously gated the direct queue→bundle handler binding on
`nitro.options.dev`, so a self-hosted (non-Vercel) production build with a
configured custom workflow world never registered it. Jobs the world
dispatched to the flow route came back `400 {"error":"Unhandled queue"}`
and runs sat in `pending` forever, with `eve dev --no-ui` as the only
workaround.

The real axis is who drives the queue: the local/configured world drives
it in-process in both `eve dev` and self-hosted `eve start`, whereas Vercel
dispatches through its managed queue trigger over HTTP. Register the binding
whenever the local world drives the queue — dev, or a non-Vercel build with
a configured world — and never for Vercel-managed deploys, preserving that
path exactly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
`pnpm add @workflow/world-postgres` resolves to npm latest (4.x), which is
incompatible with the `@workflow/*` 5.0.0-beta line eve bundles. The
mismatch surfaced as a cryptic `ZodError: invalid_union` deep in workflow
replay, with no hint about versions.

Add a boot-time, best-effort compatibility guard: when a custom world is
configured, eve reads the world's declared `@workflow/core` (or
`@workflow/world`) line and compares it against the line from eve's own
package.json (single source of truth, via `resolveExpectedWorkflowVersion`).
On a definite major / prerelease-line mismatch it throws an actionable error
naming the fix; when versions can't be determined it no-ops to avoid
false-positive boot failures. The comparison lives in a pure, exported,
unit-tested helper (`assertWorkflowWorldCompatibility`); no new runtime
dependency is added. The deeper, fully version-aware fix is being proposed
upstream in `@workflow/core`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
eve-docs Ready Ready Preview, Comment, Open in v0 Jun 25, 2026 8:46pm

@github-actions

Copy link
Copy Markdown
Contributor

Bundle + Package Summary: apps/fixtures/weather-agent

Key takeaways

  • No notable deltas vs main (293d003).

Delta vs main (293d003)

Area Metric Baseline Current Delta
Package Packed tarball 3.43 MB 3.43 MB +1.7 kB ⚠️
Package Unpacked publish size 12.45 MB 12.45 MB +5.9 kB ⚠️
Package Installed footprint 52.43 MB 52.44 MB +5.9 kB ⚠️
Package Published files 2314 2316 +2
Package Installed files 5522 5524 +2
Runtime Unique function payloads 2 2 0
Runtime Total function bytes 9.62 MB 9.62 MB -176 B ✅
Runtime Public routes 9 9 0
Changed function payloads vs main (293d003) (2)
Function Status Baseline Current Delta Route changes
functions/__server.func changed 3.98 MB 3.98 MB -144 B ✅ none
functions/.well-known/workflow/v1/flow.func changed 5.64 MB 5.64 MB -32 B ✅ none
Build Metadata
  • Preset: vercel
  • Nitro: nitro@3.0.260610-beta
  • Output directory: apps/fixtures/weather-agent/.vercel/output
  • Build metadata timestamp: 2026-06-25T20:46:59.143Z
  • Route aliases: 9 public, 1 internal (10 total aliases)
  • Vercel routes in config: 10
  • Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down

Package Details

  • Package: eve@0.14.0
  • Package directory: packages/eve
  • Tarball: 3.43 MB (eve-0.14.0.tgz)
  • Unpacked payload: 12.45 MB across 2316 published files
  • Installed footprint: 52.44 MB across 5524 installed files
  • Installed root package: 11.18 MB
  • Installed dependencies: 41.26 MB
  • Runtime dependencies: 1
  • Peer dependencies: 12 (11 optional)

Installed footprint is measured from an isolated temporary npm install of the packed tarball.

Heavy installed dependencies

  • @rolldown/binding-linux-x64-gnu: 20.26 MB (38.6%)
  • eve: 11.18 MB (21.3%)
  • ai: 6.26 MB (11.9%)
  • zod: 5.04 MB (9.6%)
  • nitro: 2.41 MB (4.6%)
Publish payload breakdown
Published file size
🟠 dist/src/compiled/experimental-ai-sdk-code-mo... [####....................] 1.51 MB 12.1%
🟡 dist/src/compiled/@workflow/core/runtime.js      [##......................] 788.4 kB 6.3%
🟡 dist/src/compiled/@vercel/sandbox/index.js       [##......................] 632.0 kB 5.1%
🟡 dist/src/compiled/@chat-adapter/slack/index.js   [#.......................] 438.4 kB 3.5%
🟡 dist/src/compiled/_chunks/workflow/attribute-... [#.......................] 371.6 kB 3.0%
🔴 Other published files                            [########################] 8.72 MB 70.0%
Installed footprint breakdown
Installed package size
🔴 @rolldown/binding-linux-x64-gnu [########################] 20.26 MB 38.6%
🔴 eve                             [#############...........] 11.18 MB 21.3%
🔴 ai                              [#######.................] 6.26 MB 11.9%
🔴 zod                             [######..................] 5.04 MB 9.6%
🟠 nitro                           [###.....................] 2.41 MB 4.6%
🟡 rolldown                        [#.......................] 771.7 kB 1.5%
🔴 Other installed packages        [########................] 6.52 MB 12.4%
Runtime dependencies (1)
Package Range Notes
nitro 3.0.260610-beta
Peer dependencies (12)
Package Range Notes
@opentelemetry/api ^1.0.0 optional peer
@sveltejs/kit ^2.0.0 optional peer
ai catalog:
braintrust ^3.0.0 optional peer
just-bash ^3.0.0 optional peer
microsandbox ^0.5.0 optional peer
next ^16.0.0 optional peer
nuxt ^4.0.0 optional peer
react ^19.0.0 optional peer
svelte ^5.0.0 optional peer
vite ^8.0.0 optional peer
vue ^3.5.0 optional peer
Function Drill-Down

Payload Size Graph

Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func     [########################] 5.64 MB 58.6%
🔴 functions/__server.func                         [#################.......] 3.98 MB 41.4%

Top Function Payloads

🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 5.64 MB
Metric Value
Public routes /.well-known/workflow/v1/flow
Runtime nodejs24.x
Handler index.mjs
Payload 5.64 MB
Function files 5.64 MB across 26 files
Traced dependencies 0 B
Signal 🟠 Bundled file __eve_nitro_handler__.mjs is 1.87 MB (33.2%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 __eve_nitro_handler__.mjs              [########################] 1.87 MB 33.2%
🟠 _chunks/runtime.mjs                    [#############...........] 975.4 kB 17.3%
🟡 _chunks/sandbox.mjs                    [##########..............] 766.0 kB 13.6%
🟡 _chunks/attribute-changes-DUxG-Gic.mjs [######..................] 473.2 kB 8.4%
🟡 _libs/@ai-sdk/gateway+[...].mjs        [#####...................] 413.5 kB 7.3%
🟠 Other bundled files                    [###############.........] 1.14 MB 20.2%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x",
  "environment": {
    "NODE_OPTIONS": "--experimental-require-module"
  },
  "maxDuration": "max",
  "experimentalTriggers": [
    {
      "type": "queue/v2beta",
      "topic": "__eve_wkf_workflow_*",
      "consumer": "default",
      "retryAfterSeconds": 5,
      "initialDelaySeconds": 0
    }
  ]
}

🟠 functions/__server.func • 8 public routes, 1 internal alias • 3.98 MB
Metric Value
Public routes /
/eve/v1/callback/[token]
/eve/v1/connections/[name]/callback/[token]
/eve/v1/health
/eve/v1/info
/eve/v1/session
/eve/v1/session/[sessionId]
/eve/v1/session/[sessionId]/stream
Internal aliases /__server
Runtime nodejs24.x
Handler index.mjs
Payload 3.98 MB
Function files 3.98 MB across 21 files
Traced dependencies 0 B
Signal 🟠 Bundled file index.mjs is 1.51 MB (37.9%)

🟠 🔎 Dependency Analysis

📦 Bundled files:

Bundled file size
🟠 index.mjs                              [########################] 1.51 MB 37.9%
🟠 _chunks/runtime.mjs                    [##############..........] 883.8 kB 22.2%
🟠 _chunks/sandbox.mjs                    [############............] 766.0 kB 19.2%
🟡 _chunks/attribute-changes-DUxG-Gic.mjs [#######.................] 448.9 kB 11.3%
⚪ _libs/zod.mjs                          [##......................] 114.2 kB 2.9%
🟡 Other bundled files                    [####....................] 258.8 kB 6.5%

🧾 Vercel Config

{
  "handler": "index.mjs",
  "launcherType": "Nodejs",
  "shouldAddHelpers": false,
  "supportsResponseStreaming": true,
  "runtime": "nodejs24.x"
}

@pranaygp

Copy link
Copy Markdown
Contributor Author

Part of the self-hosted workflow-world improvements (from a self-hosting writeup). Pairs with the docs in #306 (reverse-proxy /.well-known/workflow/ forwarding + world version-pin guidance). The shallow boot-time guard added here is a stopgap; the durable core↔world compatibility check is proposed upstream in vercel/workflow#2638.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@AndrewBarba AndrewBarba merged commit e5ccf93 into main Jun 25, 2026
37 checks passed
@AndrewBarba AndrewBarba deleted the pgp/eve-self-host-workflow-runtime branch June 25, 2026 21:04
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.

3 participants