vercel / vercel/workflow

Replay delivered after a terminal step_failed can exit without any journal write — run stays 'running' forever (observed core 5.0.0-beta.24)

Open
#3,747 0 comments 0 reactions 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

Summary

A production run wedged permanently in running after its doStreamStep (from @workflow/ai) failed terminally: the engine did re-deliver the run twice afterwards (both wait_completed events from armed fire-and-forget sleeps), but both replay invocations exited without writing a single journal event — no step_created, no run_failed, no run_completed, no suspension. The run sat running for 15+ hours with zero pending steps and a terminal step_failed as the newest step event.

Filed as documentation of the shape: we observed this on core 5.0.0-beta.24, which is far behind current (beta.43), and we know substantial replay/delivery work has landed since (#3139 → #3198, #2995, #3078). The question is whether this class — replay hydrates a terminal step failure, rejects it into the body, and the invocation then dies silently — is covered by those fixes or is a distinct gap. Happy to re-test on latest and provide anything else; same reporter/app as #3183.

Environment

  • @workflow/core@5.0.0-beta.24 (workflowCoreVersion from the run record), workflow@5.0.0-beta.20, @workflow/ai@5.0.0-beta.11 (patched — batching/telemetry hunks; the failing step and the surrounding catch are unmodified paths)
  • world: vercel (managed), Next.js app, Node runtime
  • runId wrun_01M0J19W6PMED3H23229ZQDE1A, deployment dpl_EGzJqrTTiKAADMA1aipAtMKrXE5V (team automatio-ai) — shared for engine-side lookup

Journal timeline (from workflow inspect events, all times CEST)

11:55:57.535  step_created   doStreamStep (…WQGF)
11:55:57.535  step_started   (attempt 1)
11:56:10-49   step_retrying / step_started  (attempts 2-4)
11:56:59.719  step_failed    — error: 'The prompt is too long: 263607 > 262143' (app-side cause, deterministic)
12:04:26.102  wait_completed (…WQDX — fire-and-forget sleep armed 11:39)
12:14:50.518  wait_completed (…WQFS — fire-and-forget sleep armed 11:49)
— nothing further, ever. Run status 'running' 15h+ until we intervened app-side.

19 of 20 steps completed, 1 failed, 0 pending. Both waits delivered (the events are journaled), so this is not a lost-delivery/dormancy case like #3183 — the invocations ran.

What the app body does at that point

The workflow body awaits the step, catches the hydrated error (it arrives as FatalError: Step "…doStreamStep" failed after 3 retries: …), logs, and then calls another "use step" function as part of its error path. Our function logs show each replay reached the catch (the log line inside it is the invocation's last stdout line) — but no step_created for the follow-up step was ever journaled. So the invocation died somewhere between body-level catch code and the next step registration/suspension commit, twice, identically, with HTTP-level success from the platform's perspective and nothing recorded engine-side.

Reading dist/step.js, the step_failed consumer rejects the hydrated error via ctx.promiseQueue — that part clearly worked (our catch observed the correct error). The silence begins after user code resumes from that rejection.

Why this matters

A run in this state is undetectable and unrecoverable from within the framework: not failed (no run_failed), not completed, no pending primitive for the queue to re-drive beyond already-armed timers — and further deliveries repeat the same silent death deterministically. We shipped an app-side watchdog (external liveness sweep) as containment, but the engine-side invariant "a delivered replay either completes, fails, or suspends the run — always journaling something" seems like the right place for a backstop.

Cross-refs

  • #3183 (ours; different shape — there deliveries never came; here they came and died)
  • #2994 (adjacent: step failure publication lost — here publication succeeded, replay-of-failure wedged)

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 the replay behavior on the current version, then inspect dist/step.js around the step_failed consumer and ctx.promiseQueue, along with the replay and delivery paths discussed in #3139, #3198, #2995, #3078, and #3183. Done means a delivered replay cannot exit silently: it must journal completion, failure, or suspension, with coverage for a hydrated terminal step failure followed by user catch code.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, node.js, typescript
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.