diff --git a/CHANGELOG.md b/CHANGELOG.md index d5cf59ceb48..6efbcad34ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,7 @@ Docs: https://docs.openclaw.ai - Gateway/performance: cache plugin metadata fingerprints and stable plugin index fingerprints, borrow read-only session metadata safely, keep the active session working store hot, keep status on a bounded fast path, and preserve model auth profile suffixes. (#86439) - Package/install/release: align npm package exclusions and inventory, omit unpacked test helpers, skip Homebrew until macOS packages need it, cap tsdown heap in containers, bound install/release smoke waits, and harden post-publish verification. - Codex/Auth: bound ChatGPT OAuth token exchange and refresh requests, and honor cancellation across Codex and Anthropic OAuth login flows. -- QA/E2E/CI: bound Telegram, kitchen-sink, Open WebUI, ClawHub, MCP, Discord, realtime, labeler, and GitHub API waits; fail empty explicit test, live-media, gateway CPU, plugin gauntlet, and beta-smoke runs instead of false-greening. +- QA/E2E/CI: bound Telegram, kitchen-sink, Open WebUI, ClawHub, MCP, Discord, realtime, labeler, and GitHub API waits; fail empty explicit test, live-media, gateway CPU, startup benchmark, plugin gauntlet, and beta-smoke runs instead of false-greening. - Agents/Codex: keep spawned agent bootstrap files rooted in the agent workspace while running task commands, transcripts, and compaction from the requested cwd. (#87218) Thanks @mbelinky. ## 2026.5.26 diff --git a/scripts/bench-cli-startup.ts b/scripts/bench-cli-startup.ts index b7a0e99114f..8bf6c11b1ac 100644 --- a/scripts/bench-cli-startup.ts +++ b/scripts/bench-cli-startup.ts @@ -3,6 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts"; type CommandCase = { id: string; @@ -377,7 +378,11 @@ function parseFlagValue(flag: string): string | undefined { if (idx === -1) { return undefined; } - return process.argv[idx + 1]; + const value = process.argv[idx + 1]; + if (!value || value.startsWith("--")) { + throw new Error(`${flag} requires a value`); + } + return value; } function hasFlag(flag: string): boolean { @@ -394,26 +399,12 @@ function parseRepeatableFlag(flag: string): string[] { return values; } -function parsePositiveInt(raw: string | undefined, fallback: number): number { - if (!raw) { - return fallback; - } - const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed) || parsed < 1) { - return fallback; - } - return parsed; +function parsePositiveInt(raw: string | undefined, fallback: number, label = "value"): number { + return parseStrictIntegerOption({ fallback, label, min: 1, raw }); } -function parseNonNegativeInt(raw: string | undefined, fallback: number): number { - if (!raw) { - return fallback; - } - const parsed = Number.parseInt(raw, 10); - if (!Number.isFinite(parsed) || parsed < 0) { - return fallback; - } - return parsed; +function parseNonNegativeInt(raw: string | undefined, fallback: number, label = "value"): number { + return parseStrictIntegerOption({ fallback, label, min: 0, raw }); } function parsePresets(raw: string | undefined): string[] { @@ -826,9 +817,13 @@ function parseOptions(): CliOptions { cases, entryPrimary: parseFlagValue("--entry-primary") ?? parseFlagValue("--entry") ?? DEFAULT_ENTRY, entrySecondary: parseFlagValue("--entry-secondary"), - runs: parsePositiveInt(parseFlagValue("--runs"), DEFAULT_RUNS), - warmup: parseNonNegativeInt(parseFlagValue("--warmup"), DEFAULT_WARMUP), - timeoutMs: parsePositiveInt(parseFlagValue("--timeout-ms"), DEFAULT_TIMEOUT_MS), + runs: parsePositiveInt(parseFlagValue("--runs"), DEFAULT_RUNS, "--runs"), + warmup: parseNonNegativeInt(parseFlagValue("--warmup"), DEFAULT_WARMUP, "--warmup"), + timeoutMs: parsePositiveInt( + parseFlagValue("--timeout-ms"), + DEFAULT_TIMEOUT_MS, + "--timeout-ms", + ), json: hasFlag("--json"), output: parseFlagValue("--output"), cpuProfDir: parseFlagValue("--cpu-prof-dir"), diff --git a/test/scripts/bench-cli-startup.test.ts b/test/scripts/bench-cli-startup.test.ts index 0575d5720b0..7815e94fa36 100644 --- a/test/scripts/bench-cli-startup.test.ts +++ b/test/scripts/bench-cli-startup.test.ts @@ -65,9 +65,29 @@ describe("bench-cli-startup", () => { ]); }); - it("does not accept zero measured runs", () => { - expect(testing.parsePositiveInt("0", 5)).toBe(5); + it("rejects invalid measured run counts", () => { + expect(() => testing.parsePositiveInt("0", 5, "--runs")).toThrow( + "--runs must be an integer >= 1", + ); + expect(() => testing.parsePositiveInt("2abc", 5, "--runs")).toThrow( + "--runs must be an integer >= 1", + ); + expect(() => testing.parsePositiveInt("1.5", 5, "--runs")).toThrow( + "--runs must be an integer >= 1", + ); + expect(() => testing.parsePositiveInt("1e3", 5, "--runs")).toThrow( + "--runs must be an integer >= 1", + ); + expect(() => testing.parsePositiveInt("0x10", 5, "--runs")).toThrow( + "--runs must be an integer >= 1", + ); expect(testing.parsePositiveInt("1", 5)).toBe(1); expect(testing.parseNonNegativeInt("0", 1)).toBe(0); + expect(() => testing.parseNonNegativeInt("-1", 1, "--warmup")).toThrow( + "--warmup must be an integer >= 0", + ); + expect(() => testing.parseNonNegativeInt("0b10", 1, "--warmup")).toThrow( + "--warmup must be an integer >= 0", + ); }); });