diff --git a/scripts/test-free-shards.ts b/scripts/test-free-shards.ts index 11187876c..1762bbee3 100755 --- a/scripts/test-free-shards.ts +++ b/scripts/test-free-shards.ts @@ -349,11 +349,12 @@ export const RESERVED_CPUS = 2; export function fullSuiteJobs(): number { const raw = process.env.GSTACK_FREE_JOBS; if (raw !== undefined && raw !== '') { - const value = Number.parseInt(raw, 10); - if (!Number.isInteger(value) || value <= 0) { + // Strict digits-only: parseInt would silently truncate "2abc" -> 2 and + // "3.7" -> 3, defeating the loud-failure contract the error text claims. + if (!/^\d+$/.test(raw.trim()) || Number.parseInt(raw, 10) <= 0) { throw new Error(`GSTACK_FREE_JOBS must be a positive integer, got: ${raw}`); } - return value; + return Number.parseInt(raw, 10); } return Math.max(1, Math.min(MAX_FULL_SUITE_JOBS, os.cpus().length - RESERVED_CPUS)); } diff --git a/test/test-free-shards-sandbox-knobs.test.ts b/test/test-free-shards-sandbox-knobs.test.ts index 1de5b81b7..92279d27a 100644 --- a/test/test-free-shards-sandbox-knobs.test.ts +++ b/test/test-free-shards-sandbox-knobs.test.ts @@ -55,7 +55,9 @@ describe('test-free-shards: fullSuiteJobs (GSTACK_FREE_JOBS override)', () => { }); test('zero, negative, and non-numeric values throw loudly instead of silently defaulting', () => { - for (const bad of ['0', '-2', 'abc', 'NaN']) { + // '2abc' and '3.7' pin the strict digits-only check: parseInt would + // silently truncate them to 2 and 3, defeating the loud-failure contract. + for (const bad of ['0', '-2', 'abc', 'NaN', '2abc', '3.7']) { expect(() => withJobsEnv(bad, fullSuiteJobs)).toThrow(/positive integer/); } });