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

@VaguelySeriousVaguelySerious 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_modulesif the importer is not itself under node_modules. Application code therefore can't pull third-party dependencies into discovery, but imports withinnode_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

PagePreview
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-botBot 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
NameType
@workflow/buildersMinor
@workflow/astroPatch
@workflow/cliPatch
@workflow/nestPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/nuxtPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/vitestPatch
workflowPatch
@workflow/world-testingPatch
@workflow/corePatch
@workflow/web-sharedPatch
@workflow/webPatch

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

vercelBot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production145502391694
✅ 💻 Local Development148602081694
✅ 📦 Local Production162102271848
✅ 🐘 Local Postgres162102271848
✅ 🪟 Windows15400154
✅ 📋 Other102002121232
✅ vercel-multi-region270027
Total7384011138497

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro126028
✅ example126028
✅ express126028
✅ fastify126028
✅ hono126028
✅ nextjs-turbopack15103
✅ nextjs-webpack15103
✅ nitro126028
✅ nuxt126028
✅ sveltekit14509
✅ vite126028
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable128026
✅ express-stable128026
✅ fastify-stable128026
✅ hono-stable128026
✅ nextjs-turbopack-canary135019
✅ nextjs-turbopack-stable15400
✅ nextjs-webpack-stable15400
✅ nitro-stable128026
✅ nuxt-stable128026
✅ sveltekit-stable14707
✅ vite-stable128026
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable128026
✅ express-stable128026
✅ fastify-stable128026
✅ hono-stable128026
✅ nextjs-turbopack-canary135019
✅ nextjs-turbopack-stable15400
✅ nextjs-webpack-canary135019
✅ nextjs-webpack-stable15400
✅ nitro-stable128026
✅ nuxt-stable128026
✅ sveltekit-stable14707
✅ vite-stable128026
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable128026
✅ express-stable128026
✅ fastify-stable128026
✅ hono-stable128026
✅ nextjs-turbopack-canary135019
✅ nextjs-turbopack-stable15400
✅ nextjs-webpack-canary135019
✅ nextjs-webpack-stable15400
✅ nitro-stable128026
✅ nuxt-stable128026
✅ sveltekit-stable14707
✅ vite-stable128026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable128026
✅ e2e-local-dev-tanstack-start-128026
✅ e2e-local-postgres-nest-stable128026
✅ e2e-local-postgres-tanstack-start-128026
✅ e2e-local-prod-nest-stable128026
✅ e2e-local-prod-tanstack-start-128026
✅ e2e-vercel-prod-nest126028
✅ e2e-vercel-prod-tanstack-start126028
✅ vercel-multi-region
AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 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-actionsBot 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep141 (+16%) 🔻267 🔴 (+11%)319 🔴 (-1.8%)2328 🔴 (+440%) 🔻30
TTFSstream127 (-18%) 💚261 🔴 (-23%) 💚318 🔴 (-14%)330 (-29%) 💚30
TTFShook + stream287 (-6.8%)578 🔴 (+25%) 🔻638 🔴 (+26%) 🔻4204 🔴 (+577%) 🔻30
STSO1020 steps (1-20)183 (-3.7%)257 🔴 (-13%)304 🔴 (-9.8%)315 🔴 (-9.7%)19
STSO1020 steps (101-120)210 (-5.8%)343 🔴 (+6.9%)383 🔴 (-21%) 💚423 🔴 (-16%) 💚19
STSO1020 steps (1001-1020)613 (+1.3%)718 🔴 (-8.2%)736 🔴 (-15%) 💚862 🔴 (-22%) 💚19
WO1020 steps425344 (-20%) 💚425344 (-20%) 💚425344 (-20%) 💚425344 (-20%) 💚1
SLstream latency91 (+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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep122 (±0%)225 🔴 (-6.3%)257 (-21%) 💚429 (±0%)30
TTFSstream112 (-28%) 💚219 🔴 (-35%) 💚286 (-22%) 💚309 (-33%) 💚30
TTFShook + stream331 (+7.5%)458 🔴 (-0.9%)515 🔴 (+1.4%)711 🔴 (+14%)30
STSO1020 steps (1-20)209 (+10%)356 🔴 (+21%) 🔻384 🔴 (+14%)479 🔴 (+37%) 🔻19
STSO1020 steps (101-120)241 (+8.1%)373 🔴 (+16%) 🔻436 🔴 (-9.9%)519 🔴 (+3.6%)19
STSO1020 steps (1001-1020)469 (-22%) 💚588 🔴 (-25%) 💚624 🔴 (-28%) 💚643 🔴 (-42%) 💚19
WO1020 steps434132 (-18%) 💚434132 (-18%) 💚434132 (-18%) 💚434132 (-18%) 💚1
SLstream latency108 (+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 ownersJuly 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 mainJul 23, 2026
240 of 247 checks passed
@VaguelySerious
VaguelySerious deleted the peter/discover-node-modules-opt-out branch July 23, 2026 22:57
@github-actionsgithub-actionsBot mentioned this pull request Jul 23, 2026
github-actionsBot 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 freeto 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

@VaguelySerious@karthikscale3