Understanding infinite loop with selector

I’m not sure I understand Selectors as the following code snippet has led to an infinite loop that I can’t explain why or how it happened (it’s only happened once)

for !state.IsCutoffReached {
    logger.Info("Waiting for cutoff...", "state", state)

    workflow.NewSelector(ctx).
       AddFuture(state.getCutoffTimer(ctx), func(_ workflow.Future) {
          currentEndTime := state.C.EndAt

          c, err := activities.GetBaz(ctx, state.ID)
          if err != nil {
             logger.Error("Failed to refetch baz", "state", state, "error", err)
          }
          state.C = c

          state.IsCutoffReached = !state.C.EndAt.After(currentEndTime)
       }).
       AddReceive(workflow.GetSignalChannel(ctx, workflows.SignalA), func(c workflow.ReceiveChannel, _ bool) {
          signal := workflows.Receive[workflows.Foo](c, ctx)
          state.startChildWorkflows(ctx, signal)
       }).
       AddReceive(workflow.GetSignalChannel(ctx, workflows.SignalB), func(c workflow.ReceiveChannel, _ bool) {
          if !state.IsX {
             signal := workflows.Receive[workflows.Bar](c, ctx)

             state.IsX = true
          }
       }).
       Select(ctx)
}

Basically I’m recreating a selector in a loop as I’m setting a Timer that fires when a specific entity’s (that exists in a separate Postgres DB) end_at is reached. This entity can be updated so when the first Timer fires I check to see if the end_at datetime was updated, if so recreate the selector and wait again.

What happened is I got into an infinite loop where the selector just kept getting recreated with no signal or timers firing.

Thank you.

I don’t see anything obvious. Are you sure that getCutoffTimer doesn’t return a timer that already fired? If you create a reproduction we can take a look.

I can see the timer continually get recreated with the correct date in the future. e.g. from temporal UI:
”Start To Fire Timeout 1 month, 24 days, 7 hours, 25 minutes, 25 seconds"

And I just see Timer Started

    {
      "eventId": "51191",
      "eventTime": "2025-08-22T21:41:04.379573623Z",
      "eventType": "EVENT_TYPE_TIMER_STARTED",
      "version": "1760",
      "taskId": "652228181",
      "userMetadata": {
        "summary": {
          "metadata": {
            "encoding": "json/plain"
          },
          "data": "campaign-cutoff-timer"
        }
      },
      "timerStartedEventAttributes": {
        "timerId": "51191",
        "startToFireTimeout": "4778335.642158254s",
        "workflowTaskCompletedEventId": "51190"
      }
    },

I think this is the culprit. The callback is called, but the signal is not consumed, so the next call to Select calls this callback again.

Would it be correct to do this instead then if I still only want to do whats in the conditional once even if the signal occurs N times?

ddReceive(workflow.GetSignalChannel(ctx, workflows.SignalB), func(c workflow.ReceiveChannel, _ bool) {
          signal := workflows.Receive[workflows.Bar](c, ctx)
          if !state.IsX {
             ...
             state.IsX = true
          }
       }).

Yes, that works.

That makes sense, thank you!