Skip to content

[Miniflare][Workflows] Completed steps leave timeout timers active, exhausting the 10,000-timer quota #15788

Description

@alexminza

What versions & operating system are you using?

Output collected on 2026-09-22 with the requested command:

npx envinfo --system --npmPackages '{wrangler,create-cloudflare,miniflare,@cloudflare/*}' --binaries
  System:
    OS: macOS 27.0
    CPU: (10) arm64 Apple M1 Pro
    Memory: 72.38 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 26.9.0 - /opt/homebrew/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 12.0.2 - /opt/homebrew/bin/npm
    pnpm: 12.5.1 - /opt/homebrew/bin/pnpm
    bun: 1.4.2 - /opt/homebrew/bin/bun

The command emitted no npmPackages section in this npm workspace checkout.
Installed versions were checked separately with npm ls --json:

  • Wrangler 4.135.0; @cloudflare/vitest-plugin 1.1.13;
    @cloudflare/workers-types 5.20260920.1.
  • Miniflare 5.20260918.0-alpha; workerd 1.20260918.1.
  • create-cloudflare is not installed in this checkout.

The standalone reproduction uses the direct Miniflare API, without Wrangler,
Vitest, remote bindings or deployment. It also failed with stable Miniflare
4.20260730.0; that separate installation is not part of the checkout report.

System requirements checked on the same date: macOS 27.0 meets the documented
macOS 13.5+ minimum. Node.js 26 is a Current release, which Wrangler explicitly
supports alongside Active and Maintenance releases. This checks the host
requirements, not whether a particular dependency release is defect-free.

Please provide a link to a minimal reproduction

https://gist.github.com/alexminza/08cc651f71a29422efa49598b37ba01e

Describe the Bug

A local Workflow running sequential, immediately successful step.do() calls
hits the active-timeout quota after 5,000 completed steps. Each step has a unique
deterministic name, a ten-minute timeout and retries disabled. There is only one
Workflow instance and one step running at a time.

Expected: all 9,000 steps complete; timeout timers belonging to completed
steps are cancelled instead of accumulating until their original deadlines.

Actual: execution stops at 5,000 steps with QuotaExceededError reporting
10,000 active timeouts. This is not a slow step reaching its timeout.

Steps to reproduce

  1. Download the gist's miniflare-workflow-timers.mjs into an empty directory.
  2. Follow the header comment's install and run instructions to request 9,000 steps.
  3. Inspect output.completed and output.error. The Workflow catches the
    exception to report progress, so terminal status: "complete" and exit code
    zero do not mean that all requested steps completed.

Verified cause

The installed source-map contents of context.ts and
lib/gracePeriodSemaphore.ts matched the corresponding upstream files at
miniflare@5.20260918.0-alpha exactly.

In packages/workflows-shared/src/context.ts, timeoutPromise() starts
scheduler.wait(timeout) without an abort signal. Successful completion aborts
the existing controller and calls TimePriorityQueue.remove(), but that only
updates the heap and SQLite bookkeeping. Neither operation cancels the scheduled
wait, and winning Promise.race() does not cancel its losing operation.

In the installed workerd version, Scheduler::wait() connects cancellation to
clearTimeoutImpl() only when supplied an abort signal. The timeout manager
enforces a limit on active timers, not on the cumulative number of steps.

Diagnostic comparison

A disposable copy of Miniflare 5.20260918.0-alpha was changed only as follows:

- await scheduler.wait(timeout);
+ await scheduler.wait(timeout, { signal: abortController.signal });
Runtime Completed / requested steps Result
Stable 4.20260730.0, unmodified 5,000 / 9,000 Active-timeout quota error
5.20260918.0-alpha, unmodified 5,000 / 9,000 Same error
Same alpha, experimental timeout cancellation 9,000 / 9,000 No error
Same patch, extended no-op run 12,000 / 12,000 No error

The alpha baseline and patched comparison were rerun while preparing this draft
with the same results. The stable row is from the earlier isolated run on the
same date, 2026-09-22. The extended run set stepLimit: 20000 on workflows.TEST
in the reproducer's options; it did not change the active-timer quota. Without
that override, the extended run stopped at the separate 10,000-step limit, not
with a timer-quota error.

One follow-up with the same override completed 10,001 steps when only the last
step awaited scheduler.wait(1) before returning. A 12,000-step variant awaiting
that delay in every step exceeded the runner's one-minute budget and is
inconclusive, not another reproduced timer-quota failure.

Limits of the diagnosis

packages/workflows-shared/src/lib/gracePeriodSemaphore.ts also starts a wait
without cancellation: the next step invalidates its timestamp, not its timer.
This is a source-level concern, not an independently reproduced second
failure
. The extended patched runs passed; the exact contribution of these
waits to quota exhaustion has not been isolated.

The one-line experiment supports the step-timeout diagnosis, not a fully
validated fix
. Actual timeout expiry, retry, pause/resume and restart semantics
remain untested with it. Source inspection of 5.20260921.0-alpha found the same
uncancelled waits, but that version was not runtime-tested here. No patch has
been retained in the application, and no deployed Cloudflare Workflows failure
has been demonstrated.

References

Please provide any relevant error logs

Unmodified baseline output on 2026-09-22, omitting only the elapsed-time field:

{
  "steps": 9000,
  "status": "complete",
  "output": {
    "completed": 5000,
    "error": {
      "name": "QuotaExceededError",
      "message": "You have exceeded the number of active timeouts you may set. max active timeouts: 10000, current active timeouts: 10000, finished timeouts: 0"
    }
  }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:miniflareRelating to Miniflarepackage:workflows-sharedRelating to the `@cloudflare/workflows-shared` package (local Workflows engine)

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions