vercel / vercel/workflow

Cross-deployment start() stamps the starter's spec version, not the executing deployment's

Open
#4,251 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.4k
Forks
365
Avg merge
2d 11h
Merged PRs (30d)
169

Description

Problem

A run's specVersion is chosen in start() (packages/core/src/runtime/start.ts: const specVersion = opts.specVersion ?? world.specVersion) and written on run_created and the queue message. That is the spec version of the SDK calling start().

When start() is called without a deploymentId, the run executes on the same deployment, so the caller's version is the executor's version and everything is consistent.

When start({ deploymentId }) targets another deployment, the run is stamped with the caller's spec version even though a different deployment — possibly on an older or newer @workflow/core — will read and write its event log. Every consumer of the spec version then reasons about the wrong party:

  • Reader gates that decide "does this run's runtime understand row X?" from run.specVersion (sealed-log noop, slot identity, and now the hook_disposed{forceClaimedBy} victim gate from #4193 / vercel/workflow-server#980) can be wrong for cross-deployment runs: a new starter stamps ≥ N onto a run executed by an older deployment that does not read N.
  • Writer behaviour keyed on the stamped version (compression ≥ 5, CBOR queue transport, attributes) is chosen by the starter but has to be honoured by the executor.

Deployments are pinned on Vercel, so there is no rolling-version window within one deployment; the discrepancy exists specifically between two different deployments.

Proposed fix

The executing deployment should determine the run's spec version. Options, roughly in order of preference:

  1. start() already probes the target deployment via the health check for a cross-deployment start (healthCheck(world, { deploymentId, ... }) in start.ts; the response carries specVersion and workflowCoreVersion, parsed in runtime/helpers.ts). Use the probed specVersion (capped at what this SDK and the World can mint) instead of world.specVersion when deploymentId !== currentDeploymentId. Same-deployment starts are unchanged. If the probe fails or the target predates a versioned health response, fall back to the lowest version both sides are known to support rather than the caller's current version.
  2. Alternatively let the target's run_started (written by the executor) settle/override the version, with Worlds treating run_created's version as provisional until then. Larger change; touches every World.

Either way, add mixed-version tests: new starter → older executor and older starter → newer executor, for the sealed-log and hook-force-claim reader gates.

Context

Surfaced while gating createHook({ experimental_force }) victims on specVersion >= 8 (#4193). An intermediate revision replaced that gate with a per-run executionContext.hookForceClaimReaderVersion attestation stamped from the probe (same pattern as hookResumeInputVersion); it was reverted to keep a single versioning mechanism and instead fix the stamping here for all gates at once. The reverted commit (0feb2fd on the hook-force-claim branch) shows how the probe value can be threaded into start().

🤖 Generated with Claude Code

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/core/src/runtime/start.ts and inspect the health-check response parsing in runtime/helpers.ts, including the reverted 0feb2fd revision for how probe data reaches start(). Add mixed-version coverage for newer and older starters and executors, including sealed-log and hook-force-claim reader gates; done means same-deployment behavior is unchanged and cross-deployment runs use a mutually supported executing version.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
distributed-systems, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.