vercel / vercel/workflow

@ai-sdk/workflow breaks workflow bundling on Nitro/Nuxt: Dynamic require of "ajv/dist/core.js" is not supported

Open
#3,004 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.4k
Forks
365
Avg merge
2d 11h
Merged PRs (30d)
169

Description

Description

Importing @ai-sdk/workflow anywhere in a project that uses workflow/nitro (via workflow/nuxt) breaks the workflow steps bundle for every workflow in the project — including workflows that have nothing to do with @ai-sdk/workflow.

The root cause is a transitive dependency: @ai-sdk/workflow depends on ajv (confirmed via pnpm why ajv). ajv is CommonJS and uses a non-statically-analyzable require() internally to load ajv/dist/core.js. When workflow/nitro's esbuild step bundles the workflow steps as ESM, it can't resolve this dynamic require(), and generates a shim that throws at runtime instead of failing at build time.

Because the steps bundle (.nuxt/workflow/steps.mjs) is built from every file in the project containing a 'use workflow'/'use step' directive — not scoped to files that are actually reachable/used — a single unrelated file importing @ai-sdk/workflow (even an unused prototype file sitting outside server/) poisons the shared bundle for all workflows.

Environment

  • @ai-sdk/workflow: 1.0.31
  • workflow / @workflow/core: 4.6.0
  • Nuxt: 4.5.0
  • Nitro: 2.13.4
  • Vite: 8.1.5
  • Node: v24.18.0
  • Package manager: pnpm
  • ajv: 8.20.0 (pulled in transitively by @ai-sdk/workflow)

Steps to Reproduce

  1. Create a Nuxt project with workflow/nuxt configured (modules: ["workflow/nuxt"]).

  2. Create a minimal workflow that does not use @ai-sdk/workflow at all:

    // server/workflows/echo-test.ts
    export async function echoTest(items: string[]) {
      'use workflow'
      return { received: items, count: items.length }
    }
    
    // server/api/echo-test.post.ts
    import { start } from 'workflow/api'
    import { echoTest } from '../workflow/echo-test'
    
    export default defineEventHandler(async (event) => {
      const body = await readBody(event)
      const run = await start(echoTest, [body.items ?? ['a', 'b', 'c']])
      return await run.returnValue
    })
    
  3. Confirm this works fine on its own — curl -X POST http://localhost:3000/api/echo-test -d '{"items":["x","y","z"]}' returns {"received":["x","y","z"],"count":3}.

  4. Anywhere else in the project (any file, any directory — doesn't need to be imported/used by anything), add a file with 'use workflow' that imports @ai-sdk/workflow:

    import { WorkflowAgent } from '@ai-sdk/workflow'
    
    export async function chat(messages: any[]) {
      'use workflow'
      // ...
    }
    
  5. Restart the dev server (rm -rf .nuxt .output && pnpm dev to rule out cache).

  6. Repeat the curl from step 3 against echo-test again.

Expected Behavior

The echoTest workflow should keep working exactly as before — it has no relationship to @ai-sdk/workflow or ajv.

Actual Behavior

The request to echo-test fails with:

ERROR [request error] [unhandled] [POST] http://localhost:3000/.well-known/workflow/v1/flow
Error: Dynamic require of "../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/core.js" is not supported
  at (.nuxt/workflow/steps.mjs:15:9)
  at node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/ajv.js (.nuxt/workflow/steps.mjs:49:18)
  at __require2 (.nuxt/workflow/steps.mjs:19:52)
  ...
ERROR [world-local] Queue message failed (attempt 1, HTTP 500) { queueName: '__wkf_workflow_workflow//./server/workflows/echo-test//echoTest', ... }

Removing the @ai-sdk/workflow import (and the package itself, confirmed via pnpm remove @ai-sdk/workflow + full node_modules reinstall) makes the error disappear completely and echo-test returns clean output again. Workflow count in the build log also drops (e.g. from 3 workflows to 2 workflows), confirming the unrelated file was being scanned and bundled in.

Additional Notes

  • This reproduced consistently across multiple dev server restarts with .nuxt/.output cleared, so it is not a hot-reload/timing issue.
  • This may be related to the broader class of CJS/ESM interop bugs already tracked in this repo:
    • #140 — bundler fails to resolve Node.js built-ins (stream, http, etc.), treating them as browser externals
    • #258 — ERR_REQUIRE_ESM with @workflow/world-postgres on Next.js App Router
  • Two separate concerns worth splitting if useful:
    1. ajv's dynamic require() isn't handled correctly by the esbuild step-bundling pass (likely needs ajv marked external, or the step bundler needs a CJS-compatible require shim for cases like this).
    2. The step/workflow bundler appears to scan the whole project (or at least beyond the configured serverDir) for 'use workflow'/'use step' directives, rather than only files actually reachable from used entry points — meaning dead/unused files can silently poison the shared bundle for unrelated workflows.

Minimal Reproduction

Happy to share a minimal repo if useful — the repro above is ~15 lines of code across 3 files.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing with workflow/nuxt using server/workflows/echo-test.ts and server/api/echo-test.post.ts, then inspect the generated .nuxt/workflow/steps.mjs and the workflow/nitro bundling path. Compare builds with and without the @ai-sdk/workflow import and determine whether the fix belongs in handling ajv's dynamic require or in scanning unrelated workflow files. Done means echo-test works despite the unrelated import.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, nuxt, typescript, vite
Domain
backend, build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.