From bf8329130264839c4a43fdbcaaf9a5a501aab4b6 Mon Sep 17 00:00:00 2001 From: Vladimir Rogojin Date: Sat, 25 Apr 2026 21:14:54 +0200 Subject: [PATCH] fix(host): pre-flight timeout floor at MIN_TIMEOUT_MS=100 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns with agentic-hosting's tenant-side dispatcher (command-registry.ts) which rejects timeout_ms < 100 with invalid_params. Without this CLI preflight, an operator sending --timeout 50 sees a confusing two-hop error path: CLI accepts → manager forwards → tenant rejects with invalid_params far from the source. Now the CLI fails immediately with a clear message: Invalid timeout: 50 (minimum 100ms — values below this are rejected by the tenant dispatcher) Tests: +2 (rejects below 100, accepts at/above 100). 26 total in host-commands.test.ts. --- src/host/host-commands.test.ts | 18 ++++++++++++++++++ src/host/host-commands.ts | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/host/host-commands.test.ts b/src/host/host-commands.test.ts index a9e05b5..943c041 100644 --- a/src/host/host-commands.test.ts +++ b/src/host/host-commands.test.ts @@ -113,6 +113,24 @@ describe('parseTimeout', () => { expect(() => parseTimeout('abc', 5000)).toThrow(/Invalid timeout/); expect(() => parseTimeout('Infinity', 5000)).toThrow(/Invalid timeout/); }); + + // The tenant-side hm.command dispatcher (agentic-hosting + // command-registry.ts) now rejects timeout_ms < 100 with + // `invalid_params`. Pre-flight at the CLI surface so the operator + // gets a clear local error instead of a confusing two-hop reply. + it('rejects values below MIN_TIMEOUT_MS=100ms', () => { + expect(() => parseTimeout('99', 5000)).toThrow(/minimum 100ms/); + expect(() => parseTimeout('50', 5000)).toThrow(/minimum 100ms/); + expect(() => parseTimeout('1', 5000)).toThrow(/minimum 100ms/); + // Decimal that floors below the floor still rejected. + expect(() => parseTimeout('99.9', 5000)).toThrow(/minimum 100ms/); + }); + + it('accepts values at and above MIN_TIMEOUT_MS=100ms', () => { + expect(parseTimeout('100', 5000)).toBe(100); + expect(parseTimeout('100.5', 5000)).toBe(100); // floor stays at 100 + expect(parseTimeout('30000', 5000)).toBe(30000); + }); }); describe('targetPayload', () => { diff --git a/src/host/host-commands.ts b/src/host/host-commands.ts index f755aaf..73f20a0 100644 --- a/src/host/host-commands.ts +++ b/src/host/host-commands.ts @@ -81,13 +81,25 @@ function parseGlobalOpts(cmd: Command): GlobalOpts { return cmd.optsWithGlobals(); } +// Floor used by tenant-side hm.command dispatcher (agentic-hosting +// command-registry.ts MIN_TIMEOUT_MS). Pre-flighting at the CLI surface +// avoids the confusing two-hop error path: CLI → manager → tenant rejects +// with `invalid_params`. Keep aligned with agentic-hosting's constant. +const MIN_TIMEOUT_MS = 100; + function parseTimeout(raw: string | undefined, fallback: number): number { if (!raw) return fallback; const n = Number(raw); if (!Number.isFinite(n) || n <= 0) { throw new Error(`Invalid timeout: ${raw}`); } - return Math.floor(n); + const floored = Math.floor(n); + if (floored < MIN_TIMEOUT_MS) { + throw new Error( + `Invalid timeout: ${raw} (minimum ${MIN_TIMEOUT_MS}ms — values below this are rejected by the tenant dispatcher)`, + ); + } + return floored; } function parseEnvPairs(pairs: readonly string[] | undefined): Record | undefined {