Skip to content

DX: cryptic "detached ArrayBuffer" when middleware/proxy consumes workflow request body #1982

@bredacoder

Description

@bredacoder

Note: I originally filed this as a Node.js 22 bug. After deeper investigation, the real root cause is the same as #344 — middleware/proxy intercepting /.well-known/workflow/* and consuming the request body before the workflow executor reads it. I'm leaving this open as a DX issue because the current error makes it nearly impossible to diagnose without prior knowledge.

Symptom

When triggering a workflow via start() in a Next.js App Router app, the API route returns a runId successfully (HTTP 200), but the workflow never executes. The dev server logs:

[local world] Queue operation failed: TypeError: fetch failed
    at ignore-listed frames {
  [cause]: TypeError: Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer
      at ArrayBuffer.slice (<anonymous>)
}

The run sits in status: "pending" forever (visible in .workflow-data/runs/).

Actual Root Cause

The Next.js middleware (middleware.ts) or proxy (proxy.ts in Next.js 16) intercepts the WDK's internal POST /.well-known/workflow/v1/flow request. When the middleware reads/clones the request body for authentication or any other reason, the underlying ArrayBuffer is transferred (detached). When the request finally reaches the workflow route handler, the executor's internal fetch tries to read the body and fails with the detached ArrayBuffer error.

This is exactly the issue from #344 (closed), but the error message points users in completely the wrong direction:

  • Mentions Node.js/ArrayBuffer internals — implies a runtime bug
  • No mention of middleware, proxy, or .well-known/workflow/
  • Doesn't appear during the initial POST /api/workflows/... call — it appears asynchronously after the API responds 200, in a background queue log line

Why this hits Next.js 16 users especially hard

Next.js 16 introduced proxy.ts as the replacement for middleware.ts. The default scaffolded proxy.ts matcher commonly looks like:

export const config = {
  matcher: [
    '/((?!api|invites|public|assets|_next/static|_next/image|favicon.ico).*)',
  ],
}

Notice .well-known/workflow is not in the exclusion list. Existing WDK docs (Configure proxy handler) mention this but:

  1. The page title says "if applicable" — easy to skip when you don't think you have a proxy
  2. The Next.js 16 proxy.ts naming is new — Next 15 users coming back may not realize their existing config now applies under a new name
  3. There's no runtime hint linking the cryptic error to this docs page

Proposed fixes

1. Better error message (highest impact)

When the workflow executor fails to read the request body, detect the detached-ArrayBuffer case in @workflow/world-local/dist/queue.js and @workflow/core and rethrow with actionable context:

[workflow] Failed to deliver message to /.well-known/workflow/v1/flow.
The request body was detached before reaching the executor — this almost
always means a Next.js middleware/proxy is intercepting the route.

Fix: add `.well-known/workflow` to your middleware matcher exclusion list.
See: https://useworkflow.dev/docs/getting-started/next#configure-proxy-handler-if-applicable

The check can be as simple as if (err.cause?.message?.includes('detached ArrayBuffer')) in the queue's .catch() handler.

2. Runtime sanity check on withWorkflow() startup

In dev mode, withWorkflow() could:

  • Read the project's middleware.ts / proxy.ts (if present)
  • Parse the config.matcher regex
  • Test if /.well-known/workflow/v1/flow would match the matcher
  • If yes → emit a warning at server start time:
⚠  Detected middleware/proxy matcher that includes `.well-known/workflow/*`.
    Workflows will fail with cryptic ArrayBuffer errors until this is excluded.
    Update your matcher to exclude `.well-known/workflow`.

This catches the issue before the user even tries to run a workflow.

3. Docs prominence

  • Promote the proxy-exclusion section from "if applicable" to a required step in the Next.js getting-started guide
  • Add a dedicated callout for Next.js 16 (proxy.ts naming change)
  • Add a troubleshooting page entry for the exact error string Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer → links to the fix

Reproduction

  1. Next.js 16 App Router project with workflow@5.0.0-beta.5
  2. proxy.ts matcher: '/((?!api|...).*)' (does NOT exclude .well-known/workflow)
  3. Trigger any workflow via start() from an API route
  4. Workflow returns runId (200), but stays pending forever
  5. Console shows the cryptic ArrayBuffer error

Fix verified working:

// proxy.ts
export const config = {
  matcher: [
    '/((?!api|...|\\.well-known/workflow|_next/static|...).*)',
    //                  ^^^^^^^^^^^^^^^^^^ add this
  ],
}

Environment

  • Node.js: tested on both 20.20.2 and 22.18.0 — same error, same fix
  • workflow: 5.0.0-beta.5 (also reproduced on 4.2.4)
  • @workflow/next: 5.0.0-beta.5
  • Next.js: 16.2.0 (App Router, proxy.ts)

Related

Happy to send a PR for fix #1 (the error message) if maintainers are open to it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions