Skip to content

instrumentWorkflowWithSentry swallows WorkflowStepContext (ctx.attempt) in step.do callbacks #19883

Description

@danieltroger

Is there an existing issue for this?

How do you use Sentry?

Sentry SaaS (sentry.io)

Which SDK are you using?

@sentry/cloudflare

SDK Version

10.45.0

Framework Version

Cloudflare Workers with Workflows (wrangler 4.75.0, compatibility_date 2025-12-18)

Link to Sentry event

No response

Reproduction Example/SDK Setup

// Workflow classexportclassMyWorkflowextendsWorkflowEntrypoint<Env,MyParams>{
async run(event: WorkflowEvent<MyParams>,step: WorkflowStep){awaitstep.do("my step",(ctx)=>{console.log(ctx.attempt);// undefined when instrumented with Sentry, works withoutreturnPromise.resolve();});}}// Instrumented exportexportconstInstrumentedWorkflow=Sentry.instrumentWorkflowWithSentry(sentryOptions,MyWorkflow);

Steps to Reproduce

  1. Create a Cloudflare Workflow that accesses ctx.attempt in a step.do callback (see https://developers.cloudflare.com/changelog/post/2026-03-06-step-context-available/)
  2. Export the workflow directly (no Sentry) — ctx.attempt is 1 as expected
  3. Wrap it with instrumentWorkflowWithSentryctx is now undefined

Expected Result

ctx.attempt should be forwarded to the user's callback, matching the Cloudflare Workers WorkflowStep.do signature:

do<T>(name: string,callback: (ctx: WorkflowStepContext)=>Promise<T>): Promise<T>;

Actual Result

ctx is undefined in the user's callback because WrappedWorkflowStep.do strips it at two points:

  1. instrumentedCallback is typed as () => Promise<T> (no ctx parameter) — when passed to this._step.do(), the real step.do passes ctx to it, but instrumentedCallback ignores it.

  2. userCallback() is called with no arguments — even if instrumentedCallback received ctx, it wouldn't forward it.

The overload signatures on WrappedWorkflowStep.do also type the callback as () => Promise<T> instead of (ctx: WorkflowStepContext) => Promise<T>.

Suggested Fix

- const instrumentedCallback: () => Promise<T> = async () => {+ const instrumentedCallback = async (ctx: WorkflowStepContext) => {
return startSpan(
{ /* ... */ },
async span => {
try {
- const result = await userCallback();+ const result = await userCallback(ctx);

And update the overload signatures to accept (ctx: WorkflowStepContext) => Promise<T>.

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

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions