From ddad9700d763d4b42f5dd23217364a14bc8a9ea0 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Fri, 22 May 2026 15:41:30 +0100 Subject: [PATCH] fix(supervisor): compat shim for COMPUTE checkpoint type (#3703) Workloads bundled with CLI versions before v4.4.4 use a strict zod enum for `checkpoint.type` that only allows DOCKER and KUBERNETES. When a customer's runs are routed via the compute path, those old runners receive `type: "COMPUTE"` on `/snapshots/since/...` and `/dequeue` responses and fail validation - blocking silent migration of existing deployments. The workload never reads the field - only validates the shape. Rewriting COMPUTE -> KUBERNETES on the way out lets older runners keep parsing while the database and internal services keep the real value. Limited to the two workload-facing endpoints whose response includes a checkpoint; `/continue`, `/attempts/start`, `/attempts/complete` all return shapes without one. Followup to #3114. --- .../supervisor-checkpoint-type-compat.md | 6 ++++++ apps/supervisor/src/workloadServer/index.ts | 20 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .server-changes/supervisor-checkpoint-type-compat.md diff --git a/.server-changes/supervisor-checkpoint-type-compat.md b/.server-changes/supervisor-checkpoint-type-compat.md new file mode 100644 index 000000000..dd26a3e6b --- /dev/null +++ b/.server-changes/supervisor-checkpoint-type-compat.md @@ -0,0 +1,6 @@ +--- +area: supervisor +type: fix +--- + +Keep older workloads working when checkpoints are produced by the compute path diff --git a/apps/supervisor/src/workloadServer/index.ts b/apps/supervisor/src/workloadServer/index.ts index bd38cc870..66b48de01 100644 --- a/apps/supervisor/src/workloadServer/index.ts +++ b/apps/supervisor/src/workloadServer/index.ts @@ -43,6 +43,18 @@ const WorkloadActionParams = z.object({ snapshotFriendlyId: z.string(), }); +// Workloads bundled into customer task images before CLI v4.4.4 use a strict +// zod enum for checkpoint type that only allows DOCKER and KUBERNETES. The +// workload never reads this field - it only validates the response shape - so +// rewriting it to a known value keeps older runners working without affecting +// the value stored in the database or seen by internal services. +function legacifyCheckpointType(item: T): T { + if (item.checkpoint?.type === "COMPUTE") { + return { ...item, checkpoint: { ...item.checkpoint, type: "KUBERNETES" } } as T; + } + return item; +} + type WorkloadServerEvents = { runConnected: [ { @@ -384,7 +396,9 @@ export class WorkloadServer extends EventEmitter { return; } - reply.json(sinceSnapshotResponse.data satisfies WorkloadRunSnapshotsSinceResponseBody); + reply.json({ + snapshots: sinceSnapshotResponse.data.snapshots.map(legacifyCheckpointType), + } satisfies WorkloadRunSnapshotsSinceResponseBody); }, } ) @@ -409,7 +423,9 @@ export class WorkloadServer extends EventEmitter { return; } - reply.json(dequeueResponse.data satisfies WorkloadDequeueFromVersionResponseBody); + reply.json( + dequeueResponse.data.map(legacifyCheckpointType) satisfies WorkloadDequeueFromVersionResponseBody + ); }, });