Skip to content

[builders] Add opt-out for discovering workflows in node_modules#3054

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/discover-node-modules-opt-out
Jul 23, 2026
Merged

[builders] Add opt-out for discovering workflows in node_modules#3054
VaguelySerious merged 2 commits into
mainfrom
peter/discover-node-modules-opt-out

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

Adds an opt-out that stops workflow discovery from descending into node_modules.

Today, getInputFiles() excludes node_modules, but the fast-discovery import-graph walk follows imports into any dependency that declares a workflow/@workflow/* dependency and reads/scans its files (registering the workflow/step/serde files it finds). There was no way to turn that off. This PR adds one.

What's added

  • Config optiondiscoverWorkflowsInNodeModules?: boolean on BaseWorkflowConfig (default true, current behavior).
  • Env varWORKFLOW_DISCOVER_NODE_MODULES (0/false to disable). Precedence mirrors sourcemap: config > env var > default.
  • Behavior when disabled — imports from application code that resolve into node_modules are not followed, so the build never reads, scans, or descends into dependency file graphs. This skips the scanning cost and stops third-party workflow/step/serde code from being discovered.

Implementation

The gate is a single stateless rule in processImportSpecifier: when opted out, skip an import whose resolved target is under node_modules if the importer is not itself under node_modules. Application code therefore can't pull third-party dependencies into discovery, but imports within node_modules are still followed.

That last part is deliberate and load-bearing: in an installed app @workflow/core lives under node_modules, and the SDK seeds @workflow/core/runtime/run as an entry point so built-in serde classes (Run, plus its transitive serialization.js/runs.js) get registered. Because the seeded entry is itself under node_modules, it keeps traversing its own subtree — so the SDK's own serde discovery is preserved; only third-party node_modules descent is dropped.

Tests

  • fast-discovery.test.ts: a dependency's files are never read/scanned/traversed when the option is false (asserts they're absent from discoveredFiles and no parent edge into the package is recorded). Mutation-verified: fails without the gate, passes with it.
  • A companion test seeds a node_modules package as an entry point (mirroring the SDK runtime serde entry) and asserts its subtree is still discovered — proving the SDK-serde carve-out.
  • Existing src suite green (17/17 in the file); pnpm typecheck clean.

Docs

Documented WORKFLOW_DISCOVER_NODE_MODULES in docs/content/docs/v5/configuration/build-and-diagnostics.mdx.

Docs Preview

Page Preview
Build and Diagnostics → Discovery /v5/docs/configuration/build-and-diagnostics#discovery

(Preview is behind Vercel deployment protection; requires team access.)

Follow-up (not in this PR)

The option is exposed as a builders config field + env var only. Surfacing it as a first-class framework option (e.g. withWorkflow({ discoverWorkflowsInNodeModules })) is a possible follow-up; the env var already works across all integrations today.

🤖 Generated with Claude Code

Add a `discoverWorkflowsInNodeModules` config option (default true) and a
`WORKFLOW_DISCOVER_NODE_MODULES` env var to disable discovery of
`"use workflow"`/`"use step"` files inside `node_modules`. When disabled,
third-party workflow/step files are no longer registered, so they are neither
transformed nor bundled. Serde discovery and import traversal are unaffected,
preserving the SDK's own runtime serde entry point.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ec8a3ca

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

This PR includes changesets to release 16 packages
Name Type
@workflow/builders Minor
@workflow/astro Patch
@workflow/cli Patch
@workflow/nest Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/nuxt Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/vitest Patch
workflow Patch
@workflow/world-testing Patch
@workflow/core Patch
@workflow/web-shared Patch
@workflow/web Patch

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

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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 Jul 22, 2026 5:01pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 22, 2026 5:01pm
example-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-express-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-nestjs-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-sveltekit-workflow Building Building Preview, Comment Jul 22, 2026 5:01pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 22, 2026 5:01pm
workflow-swc-playground Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workflow-tarballs Ready Ready Preview, Comment Jul 22, 2026 5:01pm
workflow-web Ready Ready Preview, Comment Jul 22, 2026 5:01pm

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1455 0 239 1694
✅ 💻 Local Development 1486 0 208 1694
✅ 📦 Local Production 1621 0 227 1848
✅ 🐘 Local Postgres 1621 0 227 1848
✅ 🪟 Windows 154 0 0 154
✅ 📋 Other 1020 0 212 1232
✅ vercel-multi-region 27 0 0 27
Total 7384 0 1113 8497

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 126 0 28
✅ example 126 0 28
✅ express 126 0 28
✅ fastify 126 0 28
✅ hono 126 0 28
✅ nextjs-turbopack 151 0 3
✅ nextjs-webpack 151 0 3
✅ nitro 126 0 28
✅ nuxt 126 0 28
✅ sveltekit 145 0 9
✅ vite 126 0 28
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 154 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 128 0 26
✅ e2e-local-dev-tanstack-start- 128 0 26
✅ e2e-local-postgres-nest-stable 128 0 26
✅ e2e-local-postgres-tanstack-start- 128 0 26
✅ e2e-local-prod-nest-stable 128 0 26
✅ e2e-local-prod-tanstack-start- 128 0 26
✅ e2e-vercel-prod-nest 126 0 28
✅ e2e-vercel-prod-tanstack-start 126 0 28
✅ vercel-multi-region
App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit ec8a3ca · Wed, 22 Jul 2026 17:12:31 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 141 (+16%) 🔻 267 🔴 (+11%) 319 🔴 (-1.8%) 2328 🔴 (+440%) 🔻 30
TTFS stream 127 (-18%) 💚 261 🔴 (-23%) 💚 318 🔴 (-14%) 330 (-29%) 💚 30
TTFS hook + stream 287 (-6.8%) 578 🔴 (+25%) 🔻 638 🔴 (+26%) 🔻 4204 🔴 (+577%) 🔻 30
STSO 1020 steps (1-20) 183 (-3.7%) 257 🔴 (-13%) 304 🔴 (-9.8%) 315 🔴 (-9.7%) 19
STSO 1020 steps (101-120) 210 (-5.8%) 343 🔴 (+6.9%) 383 🔴 (-21%) 💚 423 🔴 (-16%) 💚 19
STSO 1020 steps (1001-1020) 613 (+1.3%) 718 🔴 (-8.2%) 736 🔴 (-15%) 💚 862 🔴 (-22%) 💚 19
WO 1020 steps 425344 (-20%) 💚 425344 (-20%) 💚 425344 (-20%) 💚 425344 (-20%) 💚 1
SL stream latency 91 (+1.1%) 229 🔴 (+53%) 🔻 351 🔴 (+94%) 🔻 3672 🔴 (+1503%) 🔻 30
📜 Previous results (1)

19b4978

Wed, 22 Jul 2026 16:42:59 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 122 (±0%) 225 🔴 (-6.3%) 257 (-21%) 💚 429 (±0%) 30
TTFS stream 112 (-28%) 💚 219 🔴 (-35%) 💚 286 (-22%) 💚 309 (-33%) 💚 30
TTFS hook + stream 331 (+7.5%) 458 🔴 (-0.9%) 515 🔴 (+1.4%) 711 🔴 (+14%) 30
STSO 1020 steps (1-20) 209 (+10%) 356 🔴 (+21%) 🔻 384 🔴 (+14%) 479 🔴 (+37%) 🔻 19
STSO 1020 steps (101-120) 241 (+8.1%) 373 🔴 (+16%) 🔻 436 🔴 (-9.9%) 519 🔴 (+3.6%) 19
STSO 1020 steps (1001-1020) 469 (-22%) 💚 588 🔴 (-25%) 💚 624 🔴 (-28%) 💚 643 🔴 (-42%) 💚 19
WO 1020 steps 434132 (-18%) 💚 434132 (-18%) 💚 434132 (-18%) 💚 434132 (-18%) 💚 1
SL stream latency 108 (+20%) 🔻 148 🔴 (-1.3%) 194 🔴 (+7.2%) 334 🔴 (+46%) 🔻 30

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt)

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@VaguelySerious
VaguelySerious marked this pull request as ready for review July 22, 2026 16:31
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code owners July 22, 2026 16:31
Refine the discoverWorkflowsInNodeModules opt-out: instead of reading and
scanning every dependency file and only skipping registration, stop following
imports from application code that resolve into node_modules altogether. The
build no longer reads, scans, or descends into third-party dependency graphs,
skipping that cost entirely.

Imports within node_modules are still followed, so the SDK's seeded runtime
serde entry point (which lives under node_modules in installed apps) keeps
discovering its transitive classes such as Run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious enabled auto-merge (squash) July 23, 2026 22:43
@VaguelySerious
VaguelySerious merged commit cfe7570 into main Jul 23, 2026
240 of 247 checks passed
@VaguelySerious
VaguelySerious deleted the peter/discover-node-modules-opt-out branch July 23, 2026 22:57
github-actions Bot added a commit that referenced this pull request Jul 23, 2026
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #3075. Merge conflicts were resolved by AI — please review carefully. (backport job run)

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.

2 participants