Skip to content

Commit adccd34

Browse files
authored
fix(concurrency-gate): clamp non-finite max to 1 instead of deadlocking (#4580)
createConcurrencyGate(max) fed `max` straight into Math.max(1, max). If max is NaN or Infinity (e.g. an unguarded env parse like Number(process.env.DECOPILOT_MAX_CONCURRENT_SUBAGENTS) with a malformed value), Math.max(1, NaN) is NaN, and `active < NaN` is always false -- every acquire() parks forever, deadlocking all subagent fan-out on that pod. hosted-run-concurrency.ts already guards its env parse against this; the shared gate factory itself did not.
1 parent 23fd911 commit adccd34

2 files changed

Lines changed: 15 additions & 1 deletion

File tree

apps/mesh/src/harnesses/decopilot/built-in-tools/subagent-concurrency.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ describe("createConcurrencyGate", () => {
9696
expect(gate.active).toBe(0);
9797
});
9898

99+
it("coerces a non-finite max (e.g. an unparsed env value) up to 1 instead of deadlocking", async () => {
100+
const gate = createConcurrencyGate(Number("not-a-number"));
101+
await gate.acquire();
102+
expect(gate.active).toBe(1);
103+
let admitted = false;
104+
void gate.acquire().then(() => (admitted = true));
105+
await Promise.resolve();
106+
expect(admitted).toBe(false);
107+
});
108+
99109
it("coerces a sub-1 max up to 1", async () => {
100110
const gate = createConcurrencyGate(0);
101111
await gate.acquire();

apps/mesh/src/harnesses/decopilot/built-in-tools/subagent-concurrency.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export interface ConcurrencyGate {
2828
}
2929

3030
export function createConcurrencyGate(max: number): ConcurrencyGate {
31-
const limit = Math.max(1, max);
31+
// A NaN/Infinity `max` (e.g. from an unguarded env parse) must not reach
32+
// `Math.max` here: Math.max(1, NaN) is NaN, and `active < NaN` is always
33+
// false, so every acquire() would park forever — a total deadlock instead
34+
// of the intended cap.
35+
const limit = Number.isFinite(max) ? Math.max(1, max) : 1;
3236
let active = 0;
3337
const waiters: Array<() => void> = [];
3438

0 commit comments

Comments
 (0)