Skip to content

fix(workflows): resume a waiting step once - #565

Merged
driaug merged 1 commit into
useplunk:nextfrom
joanproductivo:fix/workflow-wait-resume-once
Oct 27, 2026
Merged

driaug merged 1 commit into
useplunk:nextfrom
joanproductivo:fix/workflow-wait-resume-once

Conversation

@joanproductivo

@joanproductivo joanproductivo commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

  • handleEvent claims a wait with an updateMany conditional on WAITING before resuming it, and skips it when the count is 0. Whoever moves the step out of WAITING resumes it; a second delivery finds it claimed.
  • processTimeout claims the wait the same way, so the event and the timeout no longer both take their branch.
  • After a claim, cancelling the timeout job is best effort. BullMQ refuses to remove a job that is already running ("could not be removed because it is locked by another worker"), and that error used to leave handleEvent with the wait completed and the run not moved on. Now that job finds the wait claimed and stops.
  • Waits resumed once behave exactly as before.

One narrower window is not covered: an event arriving in the milliseconds between a wait being parked and processStepExecution re-reading it. Happy to follow up if you want it covered too.

This touches the same lines of handleEvent as #540; whichever lands second needs a small rebase, which I'll do.

Type of Change

  • feat: New feature (MINOR version bump)
  • fix: Bug fix (PATCH version bump)
  • feat!: Breaking change - new feature (MAJOR version bump)
  • fix!: Breaking change - bug fix (MAJOR version bump)
  • docs: Documentation update (no version bump)
  • chore: Maintenance/dependencies (no version bump)
  • refactor: Code refactoring (no version bump)
  • test: Adding tests (no version bump)
  • perf: Performance improvement (PATCH version bump)

Testing

  • New WorkflowExecutionService.waitResume.test.ts with 5 tests: two deliveries at once (5 rounds), a delivery nested in the loop, the event and the timeout at once, a timeout that read the wait before the event claimed it, and a timeout job that can't be removed. All 5 fail on next without the fix.
  • Full suite on next with the change: 1553 tests passing (1548 + 5). tsc --noEmit clean for api.
  • No schema or migration changes.

Checklist

  • PR title follows conventional commits format
  • Code builds successfully
  • Tests pass locally
  • Documentation updated (if needed)

Related Issues

Closes #562

🤖 Generated with Claude Code

handleEvent reads the executions waiting for an event once and then
resumes each of them. By the time it reaches one, another delivery of the
same event may already have resumed it: two notifications for the same
email at once (link scanners clicking every link, repeated clicks), or the
same event tracked again while an earlier wait of the loop was resumed.
The step was then marked completed a second time and the execution ran its
next step again, sending the follow-up email twice. A timeout firing as the
event arrived had the same effect, taking both the event and the timeout
branch.

The wait is now claimed with an update conditional on WAITING, in
handleEvent and in processTimeout, and whoever moves it out of WAITING is
the one that resumes it. Cancelling the timeout job after a claim is best
effort: BullMQ cannot remove a job that is already running, and that job
will now find the wait claimed and stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@driaug
driaug merged commit d0f5471 into useplunk:next Oct 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A workflow wait can resume twice when its event is delivered twice at once, sending the next email twice

2 participants