fix(release): bound cross-os fetch bodies

This commit is contained in:
Vincent Koc
2026-05-28 10:38:07 +02:00
parent 4c3a0292ff
commit 13dcded7c8
2 changed files with 56 additions and 3 deletions

View File

@@ -120,6 +120,7 @@ const OMITTED_QA_EXTENSION_PREFIXES = [
];
export const CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS = 120_000;
export const CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS = 10_000;
export const CROSS_OS_FETCH_BODY_MAX_CHARS = 1024 * 1024;
export const CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS = 30_000;
export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS =
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000;
@@ -2462,6 +2463,45 @@ async function configureDiscordSmoke(params) {
});
}
export async function readBoundedCrossOsResponseText(
response: Response,
maxChars = CROSS_OS_FETCH_BODY_MAX_CHARS,
): Promise<string> {
if (!response.body) {
return "";
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let text = "";
let truncated = false;
try {
while (text.length <= maxChars) {
const { done, value } = await reader.read();
if (done) {
text += decoder.decode();
break;
}
text += decoder.decode(value, { stream: true });
if (text.length > maxChars) {
text = text.slice(0, maxChars);
truncated = true;
break;
}
}
} finally {
if (truncated) {
await reader.cancel().catch(() => undefined);
} else {
reader.releaseLock();
}
}
return truncated ? `${text}\n[truncated]` : text;
}
async function waitForDiscordMessage(params) {
const deadline = Date.now() + 3 * 60 * 1000;
while (Date.now() < deadline) {
@@ -2473,7 +2513,7 @@ async function waitForDiscordMessage(params) {
},
},
);
const text = await response.text();
const text = await readBoundedCrossOsResponseText(response);
if (!response.ok) {
await sleep(2_000);
continue;
@@ -2501,7 +2541,7 @@ async function postDiscordMessage(params) {
}),
},
);
const text = await response.text();
const text = await readBoundedCrossOsResponseText(response);
if (!response.ok) {
throw new Error(`Failed to post Discord smoke message: ${text}`);
}
@@ -3292,7 +3332,7 @@ async function runDashboardSmoke(params) {
const response = await fetch(dashboardUrl, {
signal: AbortSignal.timeout(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS),
});
const html = await response.text();
const html = await readBoundedCrossOsResponseText(response);
if (
response.ok &&
html.includes("<title>OpenClaw Control</title>") &&

View File

@@ -28,6 +28,7 @@ import {
canConnectToLoopbackPort,
buildDiscordSmokeGuildsConfig,
buildRealUpdateEnv,
CROSS_OS_FETCH_BODY_MAX_CHARS,
CROSS_OS_GATEWAY_READY_TIMEOUT_MS,
CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS,
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS,
@@ -51,6 +52,7 @@ import {
parseArgs,
packageHasScript,
readInstalledVersion,
readBoundedCrossOsResponseText,
readRunnerOverrideEnv,
resolveCrossOsAgentTurnOptional,
runCommand,
@@ -87,6 +89,17 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
expect(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS).toBeGreaterThanOrEqual(10_000);
});
it("bounds cross-OS fetched response bodies", async () => {
const tail = "tail-sentinel-should-not-appear";
const response = new Response(`${"x".repeat(5000)}${tail}`);
const text = await readBoundedCrossOsResponseText(response, 128);
expect(text).toContain("[truncated]");
expect(text).not.toContain(tail);
expect(CROSS_OS_FETCH_BODY_MAX_CHARS).toBeGreaterThan(1024);
});
it("keeps gateway RPC status probes patient enough for live release startup", () => {
expect(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000);
expect(CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS).toBeGreaterThan(