fix(release): reject malformed candidate API timeouts

This commit is contained in:
Vincent Koc 2026-06-23 09:56:13 +02:00
parent a0f93cf88f
commit 023394000c
No known key found for this signature in database
2 changed files with 57 additions and 4 deletions

View file

@ -222,11 +222,14 @@ function githubApiTimeoutMs() {
if (!raw) {
return DEFAULT_GITHUB_API_TIMEOUT_MS;
}
const value = Number(raw);
if (!Number.isFinite(value) || value <= 0) {
throw new Error("OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS must be a positive number");
if (!/^[1-9]\d*$/u.test(raw)) {
throw new Error("OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS must be a positive integer");
}
return Math.trunc(value);
const value = Number(raw);
if (!Number.isSafeInteger(value)) {
throw new Error("OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS must be a positive integer");
}
return value;
}
function githubApiTimedOut(error) {

View file

@ -17,6 +17,20 @@ function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
return new Response(JSON.stringify(body), init);
}
async function withGithubApiTimeoutEnv<T>(value: string, fn: () => Promise<T>): Promise<T> {
const previous = process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS;
process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS = value;
try {
return await fn();
} finally {
if (previous === undefined) {
delete process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS;
} else {
process.env.OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS = previous;
}
}
}
describe("release candidate checklist", () => {
it("infers validation profiles from candidate tags", () => {
expect(parseArgs(["--tag", "v2026.5.14-beta.3"]).releaseProfile).toBe("beta");
@ -387,6 +401,42 @@ describe("release candidate checklist", () => {
);
});
it("uses a positive integer GitHub API timeout env", async () => {
const fetchImpl = vi.fn(async (_url: string, init?: RequestInit) => {
expect(init?.signal).toBeInstanceOf(AbortSignal);
return jsonResponse({ workflow_runs: [] });
});
await withGithubApiTimeoutEnv("2500", async () => {
await expect(
githubApi("repos/openclaw/openclaw/actions/runs", {
fetchImpl,
token: "test-token",
}),
).resolves.toEqual({ workflow_runs: [] });
});
expect(fetchImpl).toHaveBeenCalledOnce();
});
it.each(["1e3", "10.5", "0", "soon"])(
"rejects malformed GitHub API timeout env %s",
async (raw) => {
const fetchImpl = vi.fn();
await withGithubApiTimeoutEnv(raw, async () => {
await expect(
githubApi("repos/openclaw/openclaw/actions/runs", {
fetchImpl,
token: "test-token",
}),
).rejects.toThrow(
"OPENCLAW_RELEASE_CANDIDATE_GITHUB_API_TIMEOUT_MS must be a positive integer",
);
});
expect(fetchImpl).not.toHaveBeenCalled();
},
);
it("bounds GitHub API error bodies", async () => {
const fetchImpl = vi.fn(async () => {
return new Response("x".repeat(65), {