Skip to content

Commit ff350ef

Browse files
committed
fix(webapp,run-engine): replays survive gate growth across deploys
Gate capacity is four end to end (three requested gates plus the task's anonymous inline-limit gate), so replaying a three-gate run against a task that later gained an inline limit resolves to four and still enqueues. A run stored without gates now replays with the task's currently declared limits instead of a fabricated empty array that silently cleared them, matching how replays adopt the current queue and retry config.
1 parent 833f0a2 commit ff350ef

4 files changed

Lines changed: 15 additions & 6 deletions

File tree

apps/webapp/app/v3/services/replayTaskRun.server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,15 @@ export class ReplayTaskRunService extends BaseService {
142142
: undefined,
143143
concurrencyKey:
144144
overrideOptions.concurrencyKey ?? existingTaskRun.concurrencyKey ?? undefined,
145+
/**
146+
* A run that held gates replays with those same gates. A run with none
147+
* stored passes undefined (never a fabricated empty array, which reads
148+
* as "clear the named limits") so the replay honors whatever limits the
149+
* task declares now, like it honors the current queue and retry config.
150+
*/
145151
gates: Array.isArray(existingTaskRun.gates)
146152
? (existingTaskRun.gates as Array<{ queue: string; concurrencyKey?: string }>)
147-
: [],
153+
: undefined,
148154
maxAttempts: overrideOptions.maxAttempts,
149155
maxDuration: overrideOptions.maxDurationSeconds,
150156
machine:

internal-packages/run-engine/src/engine/gateParsing.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@ import { describe, expect, it } from "vitest";
22
import { parseGates } from "./gateParsing.js";
33

44
describe("parseGates", () => {
5-
it("keeps well-shaped gates and caps at three", () => {
5+
it("keeps well-shaped gates and caps at four", () => {
66
expect(
77
parseGates([
88
{ queue: "a" },
99
{ queue: "b", concurrencyKey: "shared" },
1010
{ queue: "c" },
1111
{ queue: "d" },
12+
{ queue: "e" },
1213
])
1314
).toEqual([
1415
{ queue: "a", concurrencyKey: undefined },
1516
{ queue: "b", concurrencyKey: "shared" },
1617
{ queue: "c", concurrencyKey: undefined },
18+
{ queue: "d", concurrencyKey: undefined },
1719
]);
1820
});
1921

internal-packages/run-engine/src/engine/gateParsing.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
* value can never fail a trigger or an enqueue. A gate needs a queue name within
55
* the manifest bounds (1-128 chars); a literal concurrency key must fit the same
66
* bounds, and an empty-string key means "omitted" so the gate inherits the run's
7-
* key. At most three gates apply: a task's anonymous inline-limit gate plus two
8-
* named limits.
7+
* key. At most four gates apply: three requested gates plus the task's anonymous
8+
* inline-limit gate.
99
*/
1010
export type ParsedGate = { queue: string; concurrencyKey?: string };
1111

@@ -30,5 +30,5 @@ export function parseGates(gates: unknown): ParsedGate[] {
3030
return [{ queue, concurrencyKey }];
3131
});
3232

33-
return parsed.slice(0, 3);
33+
return parsed.slice(0, 4);
3434
}

internal-packages/run-engine/src/engine/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ export type TriggerParams = {
332332
sdkVersion?: string;
333333
cliVersion?: string;
334334
concurrencyKey?: string;
335-
/** Other queues this run must also hold a concurrency slot in while executing. At most two. */
335+
/** Other queues this run must also hold a concurrency slot in while executing. At
336+
* most four: three requested gates plus the task's anonymous inline-limit gate. */
336337
gates?: QueueGate[];
337338
workerQueue?: string;
338339
region?: string;

0 commit comments

Comments
 (0)