Skip to content

TypeScript SDK: Namespace mismatch between events.push() and ctx.waitFor() #2970

@rgarver

Description

@rgarver

Bug Description

When using namespaces with the Hatchet TypeScript SDK, events.push() auto-namespaces event keys via applyNamespace(), but ctx.waitFor() in durable tasks does not apply the namespace to the eventKey condition. This causes a mismatch where pushed events can never be matched by waiting workflows.

Reproduction

  1. Initialize Hatchet client with a namespace:

    const hatchet = HatchetClient.init({ token, namespace: 'myapp' })
  2. Create a durable task that waits for an event:

    const task = hatchet.durableTask({
      name: 'my-task',
      fn: async (input, ctx) => {
        const result = await ctx.waitFor(
          Or({ eventKey: 'my-event' }, { sleepFor: '30m' })
        )
        // Never receives the event
      },
    })
  3. Push an event from another part of the application:

    await hatchet.events.push('my-event', { data: 'hello' })

Expected Behavior

The workflow should receive the event. Both events.push() and ctx.waitFor() should handle namespacing consistently — either both apply the namespace or neither does.

Actual Behavior

  • events.push('my-event', ...) sends the event as myapp_my-event (namespaced via applyNamespace() in event-client.js:38)
  • ctx.waitFor(Or({ eventKey: 'my-event' }, ...)) registers a wait for my-event (NOT namespaced — conditionsToPb() in transformer.js passes eventKey directly without calling applyNamespace())
  • The event key myapp_my-event never matches the wait condition my-event, so the workflow times out

Root Cause

In v1/conditions/transformer.js, the conditionsToPb() function passes condition.eventKey directly to userEventKey without calling applyNamespace():

// transformer.js line ~30
userEventConditions.push({
  base: { ...baseToPb(condition.base), expression: condition.expression || '' },
  userEventKey: condition.eventKey,  // <-- NOT namespaced
});

Meanwhile, event-client.js always namespaces in push():

// event-client.js line ~38
push(type, input, options = {}) {
  const namespacedType = applyNamespace(type, this.config.namespace);
  // ...
}

Workaround

Manually apply the namespace to the event key before passing it to waitFor():

function applyNamespace(eventKey: string): string {
  const raw = process.env.HATCHET_NAMESPACE
  if (!raw) return eventKey
  const ns = raw.toLowerCase() + (raw.endsWith('_') ? '' : '_')
  return eventKey.startsWith(ns) ? eventKey : `${ns}${eventKey}`
}

// In workflow:
const eventKey = applyNamespace(`agent:${sessionId}:message`)
await ctx.waitFor(Or({ eventKey }, { sleepFor: '30m' }))

Environment

  • SDK version: @hatchet-dev/typescript-sdk@1.10.8
  • Node.js: v22
  • Using Hatchet Cloud

Impact

This completely breaks durable event-driven workflows when namespaces are used. Without the workaround, subsequent messages to running workflows are silently lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions