Cross-deployment start() stamps the starter's spec version, not the executing deployment's
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-lognoop, slot identity, and now thehook_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:
start()already probes the target deployment via the health check for a cross-deployment start (healthCheck(world, { deploymentId, ... })instart.ts; the response carriesspecVersionandworkflowCoreVersion, parsed inruntime/helpers.ts). Use the probedspecVersion(capped at what this SDK and the World can mint) instead ofworld.specVersionwhendeploymentId !== 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.- Alternatively let the target's
run_started(written by the executor) settle/override the version, with Worlds treatingrun_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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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