diff --git a/lib/gbrain-supabase-provision.ts b/lib/gbrain-supabase-provision.ts index 7355e4abd..76eecbff4 100644 --- a/lib/gbrain-supabase-provision.ts +++ b/lib/gbrain-supabase-provision.ts @@ -411,6 +411,12 @@ async function cmdWait(ctx: Ctx, args: string[]): Promise { else ref = arg; } if (!ref) die(ctx, 'wait: missing '); + // Validate up front: NaN would make the deadline comparison below always + // false and the poll loop run forever (the bash predecessor errored here). + const timeoutSeconds = Number(timeout); + if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 0) { + die(ctx, 'wait: --timeout must be a non-negative integer (seconds)'); + } requirePat(ctx); @@ -439,7 +445,7 @@ async function cmdWait(ctx: Ctx, args: string[]): Promise { ctx.stderr(`${PROG}: unexpected status '${status}' — continuing to poll\n`); } - if (elapsed >= Number(timeout)) { + if (elapsed >= timeoutSeconds) { ctx.stderr(`${PROG}: wait timed out after ${timeout}s (last status: ${status})\n`); ctx.stderr(`${PROG}: re-run with /setup-gbrain --resume-provision ${ref}\n`); throw new ExitError(6); diff --git a/test/gbrain-supabase-provision.test.ts b/test/gbrain-supabase-provision.test.ts index 0cff7f1e0..a0d2a981e 100644 --- a/test/gbrain-supabase-provision.test.ts +++ b/test/gbrain-supabase-provision.test.ts @@ -360,6 +360,17 @@ describe('wait', () => { expect(r.stderr).toContain('wait timed out'); expect(r.stderr).toContain('--resume-provision abc'); }); + + test('non-numeric --timeout dies at parse time instead of polling forever', async () => { + // NaN would make `elapsed >= timeout` always false: an infinite 5s poll loop. + // The bash predecessor errored on `[ "$elapsed" -ge "abc" ]`; the port + // must be at least as strict. + const r = await runCmd(['wait', 'abc', '--timeout', 'abc'], { + SUPABASE_ACCESS_TOKEN: 'sbp_test', + }); + expect(r.status).toBe(2); + expect(r.stderr).toContain('--timeout must be a non-negative integer'); + }); }); describe('pooler-url', () => {