Vercel Workflow Continuation Versioning
A run is pinned to the deployment that started it, and the only supported way off that pin is start(fn, args, { deploymentId: "latest" }), whose own type declaration in workflow 4.6.2 states that argument and return types become unknown because there is no guarantee the types will be consistent across deployments. So the continuation state of a self-restarting loop is an untyped wire format between two versions of your own code, and the compiler goes quiet at exactly the boundary that needs it. What happens next is silent shape drift: a deploy renames lastSentAt to lastDigestAt, an in-flight loop hands the new code the old object, the field reads undefined, the digest window falls back to the epoch, every subscriber gets a year of articles at once, and the loop writes the broken shape forward so the next hop repeats it, all without throwing. This stamps the state with a version and a hop count and refuses at the boundary rather than coercing: forward migrations run in sequence and are validated after running, so a lossy migration is rejected instead of shipped; state stamped with a version the running code has never heard of is refused loudly, because a rollback moves state backward and migrations only run forward and there is no correct guess for a field you cannot know about; and the hop cap is the brake on an always-continuing chain that no single cancellation stops, since each hop is a fresh run. Pure logic, no SDK imports, with a demo that runs under bun. Pinned to [email protected].
npx shadcn@latest add https://ui.aryank.space/r/vercel-workflow-continuation-versioning.jsonInstalls from ui.aryank.space. To add it by hand, copy the files in Files below, or register the @compronents namespace via the docs.
Deploy 12 renamed lastSentAt to lastDigestAt. The loop started on deploy 11 hands the new code the old object, the field reads undefined, and the digest window falls back to the epoch. Nothing throws. Every subscriber receives a year of articles in one email, and the loop writes the same broken shape forward so the next hop does it again.