Skip to content

Self-hosted video processing silently broken: .well-known/workflow/v1/* route handlers return the SPA HTML shell (Turbopack standalone build) #1944

Description

@RobinHoodO

Summary

On the self-hosted Docker image (ghcr.io/capsoftware/cap-web:latest), every recording gets stuck on "Processing failed". Uploads succeed (segments + manifest.json land in the bucket), but the recording never finishes processing.

Root cause: the Workflow DevKit control-plane routes under /.well-known/workflow/v1/* are not served by their route handlers — they return the Next.js app/SPA HTML shell with HTTP 200. Because the workflow engine advances every durable step by making an HTTP self-call to /.well-known/workflow/v1/flow (and /step), and that call gets a 200 text/html page back instead of the handler, the workflow silently never advances. No error is logged; the run stays pending forever.

This appears to be a Next.js 16 + Turbopack standalone build issue: apps/web/package.json builds with next build --turbopack, and the entire .well-known namespace is unreachable at runtime in the standalone server.

Environment

  • Image: ghcr.io/capsoftware/cap-web:latest (built 2026-06-13, Next 16.2.1, workflow@4.2.0-beta.73, @workflow/world-local@4.1.0-beta.46)
  • Self-hosted via the repo docker-compose.yml (MySQL + MinIO + media-server + cap-web)
  • Recording mode: desktopSegments (in-browser recorder)

Reproduction

  1. Self-host with the provided docker-compose.
  2. Record anything via the web recorder.
  3. Segments + manifest.json (is_complete: true) upload to the bucket; the media-server is never called; the cap is shown as "Processing failed".
  4. Inside the container, .workflow-data/runs/*.json shows the run stuck at "status": "pending" (only a run_created event), and video_uploads.phase stays processing with processing_error = NULL.

Evidence (the smoking gun)

Route handlers work everywhere except the .well-known namespace. Run inside the cap-web container:

// returns 200 text/html — the SPA shell, NOT the workflow handler
fetch("http://127.0.0.1:3000/.well-known/workflow/v1/flow",
  {method:"POST",headers:{"content-type":"application/json"},body:"{}"})
  .then(async r => console.log(r.status, r.headers.get("content-type")))
Request Result
POST /api/upload/signed 401 (handler runs ✅)
POST /api/auth/providers 400 (handler runs ✅)
POST /.well-known/workflow/v1/flow 200 text/html
POST /.well-known/workflow/v1/step 200 text/html
POST /.well-known/appspecific/anything (nonexistent) 200 text/html

So the whole /.well-known/* namespace falls through to the app render — even a path with no route. The route handlers do exist in the build (/.next/server/app/.well-known/workflow/v1/flow/route.js) and are listed in app-paths-manifest.json; nothing shadows them (middleware manifest is empty; no .well-known rewrite in next.config.mjs). They simply never execute.

Because the self-call returns 200, the local queue's dispatch logs no error (it only logs on non-2xx), so the failure is completely silent.

Suspected cause & suggested fix

apps/web/package.json:

"build": "next build --turbopack",
"build:web": "next build --turbopack",

Turbopack's production/standalone build does not appear to serve app route handlers under a dot-prefixed top-level segment (.well-known). Suggested fix: build the web app with webpack (drop --turbopack), or have Turbopack emit/serve these routes correctly.

I'm validating a webpack rebuild of this exact image and will follow up / open a PR if it resolves it. Happy to provide the full .workflow-data run dump, manifests, or any other diagnostics.

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