workflowRunId.
A run ID is reserved while its run is in progress, and released once the run
finishes, fails or is cancelled.
Singleton workflows require
@upstash/workflow 1.4.0 or later.With older versions, a run started again with the same workflowRunId and the
same body within 10 minutes of the previous one is deduplicated by QStash: it
either doesn’t start or gets stuck on its first step.
In both patterns, a trigger after the previous run has ended starts a new run,
even with exactly the same body.
Keep the running one
Trigger the workflow with a fixedworkflowRunId. If a run with that ID is
still in progress, client.trigger throws and no new run is started.
client.trigger returns:
- No run in progress: a new run starts and the call resolves to
{ workflowRunId: "wfr_nightly-sync" }. - A run is in progress: the call throws a
QstashErrorwithstatus: 400. The running run is not affected.
400 for other invalid trigger requests too, such as a malformed
URL, so status alone doesn’t tell you that the ID is taken. The example checks
with client.logs that a run with the ID is in
progress before skipping. If that run ends between the two calls, runs is
empty and the error is rethrown, so you can retry the trigger.
QstashError comes from @upstash/qstash, which @upstash/workflow depends
on. If your package manager doesn’t let you import a dependency of a
dependency, add @upstash/qstash to your own dependencies. Keep it on the
version @upstash/workflow uses: with two copies of the package,
instanceof QstashError doesn’t match.Replace the running one
To always run the latest request, first cancel the existing run by its prefixed run ID (for example,wfr_rebuild-cache), then start a new one with
the unprefixed workflowRunId (for example, rebuild-cache). If the previous
run hasn’t finished yet, it is stopped and the new run takes its place.
client.cancelresolves to{ cancelled: 1 }when a run was in progress and got cancelled, and to{ cancelled: 0 }when there was nothing to cancel. It doesn’t throw in either case, so you can call it unconditionally.client.triggerresolves to{ workflowRunId: "wfr_rebuild-cache" }. The cancel is completed beforeclient.cancelreturns, so the ID is free when you trigger the new run.
context.call results of the cancelled run are
never added to the new run, even though both runs share the same ID.