You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently if you change much of anything in a workflow while there are ongoing workflows, they'll fail.
Allow configuring some things to make it more flexible, if you're willing to think through the edge cases:
Allow running new steps: if you add a new step to the beginning of a workflow, existing workflows will run it. If that affects later steps, see (4, 5)
Allow out of order steps: if steps change order but the args are the same, allow it. Use the first un-consumed result. If this was actually adding another earlier call to the "same" step (same fn & args), then the later one will re-run as if it's the new one.
Allow removing steps: if you remove a step, carry on. If that affects later steps, see (4, 5). This also covers breaking out of a "for" loop early, e.g.
Allow re-running steps if their args change (per-step opt-in): If an arg changes, re-run that step, if that step can handle retries.
Allow ignoring changed args (per-step opt-in): if you add or modify args for a function, but you're fine with existing workflows using the previous result / not running it for new args.
import{WorkflowManager}from"@convex-dev/workflow";import{components}from"./_generated/api";exportconstworkflow=newWorkflowManager(components.workflow);exportconstexampleWorkflow=workflow.define({args: {storageId: v.id("_storage"),},handler: async(step,args)=>{consttranscription=awaitstep.runAction(internal.index.computeTranscription,{storageId: args.storageId},);// Sleep for a month after computing the transcription.awaitstep.sleep(30*24*60*60*1000);constembedding=awaitstep.runAction(internal.index.computeEmbedding,{
transcription,});console.log(embedding);},});
Say a user starts this and enters the sleep for a month but then you add new steps or remove others its going to totally f-up that old workflow when it resumes after a month's sleep. That might be okay if it explicitly errors as that will kill the workflow and you can handle it. The worse scenario is that it DOESNT error and instead carries, calls some new or re-arranged steps that causes it to behave in a totally unexpected way leading to corrupted data and hard to debug issues.
I think the key issue is going to be that the steps aren't known deterministically upfront, they are only "discovered" as the workflow runs.
This means you cant deterministically "id" a workflow definition and thus determine what (if anything) has changed. The issue is simmilar to useEffect from the react world. The only way that the handler for a useEffect can be changed is by supplying a deterministic deps array as the second param. If any of the deps change then its now a different "version" of the useEffect.
The only way around all of theses issues is we make it mandatory that the user supplies a "version" identifier and a "versioningPolicy" when they define a workflow.
The policy will enforce what happens to resuming steps if the version changes. For example if we have a policy "alwaysError" then when that sleep resumes after a month, if the version of the workflow is different from the one when the workflow started it errors which is probably safer than continuing on and potentially leading to confusing scenarios.
Maybe one of the policies lets you provider a "migration" handler that allows you to do something with resuming steps if the version changes?
Another question is what happens if that sleep step is removed entirely when going from version 1 to 2 of the workflow? Should it still re-enter the workflow?
So I think its safer that we have an explicit version and a version policy to force the user to think about these things.
I agree there's a problem to solve. I don't think version number is the only way to do it. We already have a known graph of what has happened so far, and things changing after what we've done so far is fine. The problem is when something changes that has already been done, which I'd say is all captured by my numbered points above. If you can think of an edge case that doesn't fall into 1-5, let me know!
E.g. if you remove sleep, a workflow that has hit that but hasn't finished will eventually wake up and it'll be a case of (3) above, which you can opt into
Having a migration or version policy or version number all sound compelling, but I don't think version number is necessary (even if it might be sufficient)
The folks over at Temporal have given this a lot of thought, and I think their patch solution is a great approach that could be applied here. It makes the behavior of migrating from one workflow to another explicit in the code instead of relying on implicit ordering or version metadata. I haven't looked at their implementation to see if it could be applied to Convex, but thought I'd mention it as a possible solution.
While it may technically be possible to allow some edits to an existing workflow, it seems to complicate the developer's mental model of how workflows operate.
I'm migrating from AWS Step Functions, and I prefer the immutable versioning approach. That being said, it would be good to be able to change the internals of a function. Only changes to the function signature or steps would need a new version.
Since most of my workflows are short-lived, I'm personally ok with these tradeoffs:
Canceling or failing any running workflows during updates
Having Convex deployments fail if workflows are still running
The version aliasing that AWS Step Functions use may be useful here too.
we've used temporal versioning (which the TS SDK exposes as "patches") quite a bit and it works well, but does get annoying tbh. But we've also tried versioning the whole workflow (i.e. duplicating the code), and that simply doesn't cut it for long-running workflows.
You should look into their "continue-as-new" functionality though. It helps a lot with migrating over old versions.
Is it possible to keep all the finished mutations and not roll them back if a workflow fails due to an update?
My problem is that some steps include pretty expensive APIs, and I would like to avoid repeating them during the workflow update.
yes absolutely - it currently keeps all mutations. a feature to help
migrations in the future could allow selectively dropping / reordering
results, but keeping the vast majority
Activity
ianmacartney commentedon Apr 2, 2025
cc @mikecann
mikecann commentedon Apr 3, 2025
Ughh versioning is such a minefield and headache.
Like if we take the example from the workflow page as an example: https://www.convex.dev/components/workflow
Say a user starts this and enters the sleep for a month but then you add new steps or remove others its going to totally f-up that old workflow when it resumes after a month's sleep. That might be okay if it explicitly errors as that will kill the workflow and you can handle it. The worse scenario is that it DOESNT error and instead carries, calls some new or re-arranged steps that causes it to behave in a totally unexpected way leading to corrupted data and hard to debug issues.
I think the key issue is going to be that the steps aren't known deterministically upfront, they are only "discovered" as the workflow runs.
This means you cant deterministically "id" a workflow definition and thus determine what (if anything) has changed. The issue is simmilar to
useEffectfrom the react world. The only way that the handler for auseEffectcan be changed is by supplying a deterministicdepsarray as the second param. If any of the deps change then its now a different "version" of the useEffect.The only way around all of theses issues is we make it mandatory that the user supplies a "version" identifier and a "versioningPolicy" when they define a workflow.
The policy will enforce what happens to resuming steps if the version changes. For example if we have a policy "alwaysError" then when that sleep resumes after a month, if the version of the workflow is different from the one when the workflow started it errors which is probably safer than continuing on and potentially leading to confusing scenarios.
Maybe one of the policies lets you provider a "migration" handler that allows you to do something with resuming steps if the version changes?
Another question is what happens if that sleep step is removed entirely when going from version 1 to 2 of the workflow? Should it still re-enter the workflow?
So I think its safer that we have an explicit version and a version policy to force the user to think about these things.
ianmacartney commentedon Apr 3, 2025
I agree there's a problem to solve. I don't think version number is the only way to do it. We already have a known graph of what has happened so far, and things changing after what we've done so far is fine. The problem is when something changes that has already been done, which I'd say is all captured by my numbered points above. If you can think of an edge case that doesn't fall into 1-5, let me know!
E.g. if you remove sleep, a workflow that has hit that but hasn't finished will eventually wake up and it'll be a case of (3) above, which you can opt into
Having a migration or version policy or version number all sound compelling, but I don't think version number is necessary (even if it might be sufficient)
nakleiderer commentedon May 30, 2025
The folks over at Temporal have given this a lot of thought, and I think their
patchsolution is a great approach that could be applied here. It makes the behavior of migrating from one workflow to another explicit in the code instead of relying on implicit ordering or version metadata. I haven't looked at their implementation to see if it could be applied to Convex, but thought I'd mention it as a possible solution.https://docs.temporal.io/develop/typescript/versioning
adamk-au commentedon Jul 7, 2025
While it may technically be possible to allow some edits to an existing workflow, it seems to complicate the developer's mental model of how workflows operate.
I'm migrating from AWS Step Functions, and I prefer the immutable versioning approach. That being said, it would be good to be able to change the internals of a function. Only changes to the function signature or steps would need a new version.
Since most of my workflows are short-lived, I'm personally ok with these tradeoffs:
The version aliasing that AWS Step Functions use may be useful here too.
Hades32 commentedon Jul 16, 2025
we've used temporal versioning (which the TS SDK exposes as "patches") quite a bit and it works well, but does get annoying tbh. But we've also tried versioning the whole workflow (i.e. duplicating the code), and that simply doesn't cut it for long-running workflows.
You should look into their "continue-as-new" functionality though. It helps a lot with migrating over old versions.
ivryb commentedon Oct 28, 2025
Is it possible to keep all the finished mutations and not roll them back if a workflow fails due to an update?
My problem is that some steps include pretty expensive APIs, and I would like to avoid repeating them during the workflow update.
ianmacartney commentedon Oct 29, 2025