Skip to content

[Cloudflare] instrumentWorkflowWithSentry does not capture errors thrown in run() outside a step #26286

Open

Description

@DomWane

Is there an existing issue for this?

Related: #26091, #26161

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/cloudflare

SDK Version

11.12.0 (the code is the same on develop)

Framework Version

wrangler 4.156.0, compatibility_date 2026-05-01, compatibility_flags ["nodejs_compat"]

Reproduction Example/SDK Setup

import * as Sentry from '@sentry/cloudflare'
import { WorkflowEntrypoint } from 'cloudflare:workers'
import { NonRetryableError } from 'cloudflare:workflows'

class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
  async run(event, step) {
    if (!event.payload.accountId) {
      throw new NonRetryableError('accountId is required')
    }
    await step.do('load', { timeout: '1 second' }, async () => {
      // ...
    })
  }
}

export const Instrumented = Sentry.instrumentWorkflowWithSentry(
  (env) => ({ dsn: env.SENTRY_DSN }),
  MyWorkflow,
)

Steps to Reproduce

  1. Wrap a Workflow with instrumentWorkflowWithSentry.
  2. Let run() fail outside a step callback, for example by throwing before the first step or by a step timeout.
  3. Trigger an instance.

Expected Result

One Sentry event for the error that fails the instance.

Actual Result

No event. The instance fails and the error is only in the Workers logs.

dev-packages/cloudflare-integration-tests on develop, one instance per row, retries.limit: 0 on steps:

run() fails with Sentry events
TypeError thrown before any step 0
NonRetryableError thrown before any step 0
Error thrown after a successful step 0
step.waitForEvent timeout 0
step.do timeout (Execution timed out after 1000ms) 0
step.do returning a non-serializable value (Aborting engine: Value is not serialisable) 0
Error thrown inside the step.do callback 1

The run proxy in instrumentWorkflowWithSentry wraps the call in try { … } finally { waitUntil(flushAndDispose(client)) } with no catch. Only WrappedWorkflowStep.do calls captureException, and only when the callback itself throws.

Additional Context

Cloudflare docs: "Any error not handled at this top level will cause the Workflow instance to fail."

A catch in the run proxy alone is not enough: step.do rejects with a copy of the error the callback threw, so the __sentry_captured__ mark does not carry over and every failed step is reported twice. The fix tracks, per step.do call, whether the step callback already captured the error, and run skips only those rejections. Step timeouts and engine-side step failures are still reported once by run.

Contribution

I have this fix ready with unit tests and integration tests covering the cases in the table above, and can open a PR once you give the go-ahead.

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions