Skip to content

Commit 4f96fb0

Browse files
hxy91819Copilot
andcommitted
CI: narrow release-check hardening diff
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 55bf3f3 commit 4f96fb0

3 files changed

Lines changed: 92 additions & 32 deletions

File tree

.github/workflows/openclaw-cross-os-release-checks-reusable.yml

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -430,35 +430,37 @@ jobs:
430430
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN: ${{ secrets.OPENCLAW_DISCORD_SMOKE_BOT_TOKEN }}
431431
OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
432432
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
433-
CANDIDATE_FILE_NAME: ${{ needs.prepare.outputs.candidate_file_name }}
433+
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
434+
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
435+
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
434436
CANDIDATE_VERSION: ${{ needs.prepare.outputs.candidate_version }}
435437
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
436438
BASELINE_SPEC: ${{ needs.prepare.outputs.baseline_spec }}
437439
PREVIOUS_VERSION: ${{ inputs.previous_version }}
438-
BASELINE_FILE_NAME: ${{ needs.prepare.outputs.baseline_file_name }}
440+
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
439441
PROVIDER: ${{ inputs.provider }}
440-
RELEASE_SUITE: ${{ matrix.suite }}
441-
TARGET_REF: ${{ inputs.ref }}
442-
ARTIFACT_NAME: ${{ matrix.artifact_name }}
443-
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
444-
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
442+
MODE: ${{ matrix.lane }}
443+
SUITE: ${{ matrix.suite }}
444+
REF: ${{ inputs.ref }}
445+
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
445446
run: |
446447
DISCORD_ARGS=()
447448
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
448449
DISCORD_ARGS+=(--run-discord-roundtrip true)
449450
fi
450451
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
451-
--candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${CANDIDATE_FILE_NAME}" \
452+
--candidate-tgz "${CANDIDATE_TGZ}" \
452453
--candidate-version "${CANDIDATE_VERSION}" \
453454
--source-sha "${SOURCE_SHA}" \
454455
--baseline-spec "${BASELINE_SPEC}" \
455456
--previous-version "${PREVIOUS_VERSION}" \
456-
--baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${BASELINE_FILE_NAME}" \
457+
--baseline-tgz "${BASELINE_TGZ}" \
457458
--provider "${PROVIDER}" \
458-
--suite "${RELEASE_SUITE}" \
459-
--ref "${TARGET_REF}" \
459+
--mode "${MODE}" \
460+
--suite "${SUITE}" \
461+
--ref "${REF}" \
460462
"${DISCORD_ARGS[@]}" \
461-
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${ARTIFACT_NAME}-${RELEASE_SUITE}"
463+
--output-dir "${OUTPUT_DIR}"
462464
463465
- name: Summarize release checks
464466
if: always()

scripts/openclaw-cross-os-release-checks.ts

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ const providerConfig = {
5353
};
5454

5555
const PACKAGE_DIST_INVENTORY_RELATIVE_PATH = "dist/postinstall-inventory.json";
56-
const PACKAGED_QA_RUNTIME_PATHS = new Set([
57-
"dist/extensions/qa-channel/runtime-api.js",
58-
"dist/extensions/qa-lab/runtime-api.js",
59-
]);
6056
const OMITTED_QA_EXTENSION_PREFIXES = [
6157
"dist/extensions/qa-channel/",
6258
"dist/extensions/qa-lab/",
@@ -186,11 +182,13 @@ export function resolveRunnerMatrix(params) {
186182
];
187183
return {
188184
include: runners.flatMap((runner) =>
189-
suites.map((suite) => ({
190-
...runner,
191-
suite,
192-
suite_label: formatSuiteLabel(suite),
193-
})),
185+
suites.map((suite) =>
186+
Object.assign({}, runner, {
187+
suite,
188+
suite_label: formatSuiteLabel(suite),
189+
lane: suite.includes(`upgrade`) || suite === `dev-update` ? `upgrade` : `fresh`,
190+
}),
191+
),
194192
),
195193
};
196194
}
@@ -508,7 +506,7 @@ function isPackagedDistPath(relativePath) {
508506
return false;
509507
}
510508
if (OMITTED_QA_EXTENSION_PREFIXES.some((prefix) => relativePath.startsWith(prefix))) {
511-
return PACKAGED_QA_RUNTIME_PATHS.has(relativePath);
509+
return false;
512510
}
513511
return true;
514512
}
@@ -1790,8 +1788,7 @@ async function runInstalledAgentTurn(params) {
17901788
logPath: params.logPath,
17911789
timeoutMs: 10 * 60 * 1000,
17921790
});
1793-
const payloadTexts = parseAgentPayloadTexts(result.stdout);
1794-
if (!payloadTexts.some((text) => text.trim() === "OK")) {
1791+
if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) {
17951792
throw new Error("Agent output did not contain the expected OK marker.");
17961793
}
17971794
return result;
@@ -2439,27 +2436,57 @@ async function runAgentTurn(params) {
24392436
logPath: params.logPath,
24402437
timeoutMs: 10 * 60 * 1000,
24412438
});
2442-
const payloadTexts = parseAgentPayloadTexts(result.stdout);
2443-
if (!payloadTexts.some((text) => text.trim() === "OK")) {
2439+
if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) {
24442440
throw new Error("Agent output did not contain the expected OK marker.");
24452441
}
24462442
return result;
24472443
}
24482444

2445+
export function agentOutputHasExpectedOkMarker(stdout, options = {}) {
2446+
const payloadTexts = parseAgentPayloadTexts(stdout);
2447+
if (payloadTexts.some((text) => text.trim() === "OK")) {
2448+
return true;
2449+
}
2450+
if (typeof options.logPath !== "string") {
2451+
return false;
2452+
}
2453+
try {
2454+
const logTexts = parseAgentPayloadTexts(readFileSync(options.logPath, "utf8"));
2455+
return logTexts.some((text) => text.trim() === "OK");
2456+
} catch {
2457+
return false;
2458+
}
2459+
}
2460+
24492461
function parseAgentPayloadTexts(stdout) {
24502462
try {
24512463
const payload = JSON.parse(stdout);
2464+
const directTexts = [
2465+
payload?.finalAssistantVisibleText,
2466+
payload?.finalAssistantRawText,
2467+
payload?.meta?.finalAssistantVisibleText,
2468+
payload?.meta?.finalAssistantRawText,
2469+
payload?.result?.finalAssistantVisibleText,
2470+
payload?.result?.finalAssistantRawText,
2471+
payload?.result?.meta?.finalAssistantVisibleText,
2472+
payload?.result?.meta?.finalAssistantRawText,
2473+
].filter((text): text is string => typeof text === "string");
24522474
const entries = Array.isArray(payload?.payloads)
24532475
? payload.payloads
24542476
: Array.isArray(payload?.result?.payloads)
24552477
? payload.result.payloads
24562478
: [];
2457-
if (!Array.isArray(entries)) {
2458-
return [];
2459-
}
2460-
return entries.flatMap((entry) => (typeof entry?.text === "string" ? [entry.text] : []));
2479+
const payloadTexts = Array.isArray(entries)
2480+
? entries.flatMap((entry) => (typeof entry?.text === "string" ? [entry.text] : []))
2481+
: [];
2482+
return [...directTexts, ...payloadTexts];
24612483
} catch {
2462-
return stdout.trim() ? [stdout] : [];
2484+
const finalTextMatches = [
2485+
...stdout.matchAll(
2486+
/"(?:finalAssistantVisibleText|finalAssistantRawText|text)"\s*:\s*"([^"]*)"/gu,
2487+
),
2488+
].map((match) => match[1]);
2489+
return finalTextMatches.length > 0 ? finalTextMatches : stdout.trim() ? [stdout] : [];
24632490
}
24642491
}
24652492

test/scripts/openclaw-cross-os-release-checks.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
22
import { createServer as createNetServer } from "node:net";
33
import { tmpdir } from "node:os";
44
import { join } from "node:path";
5+
import { setTimeout as delay } from "node:timers/promises";
56
import { describe, expect, it } from "vitest";
67
import {
8+
agentOutputHasExpectedOkMarker,
79
buildWindowsDevUpdateToolchainCheckScript,
810
buildWindowsFreshShellVersionCheckScript,
911
buildWindowsPathBootstrapScript,
@@ -37,6 +39,27 @@ import {
3739
} from "../../scripts/openclaw-cross-os-release-checks.ts";
3840

3941
describe("scripts/openclaw-cross-os-release-checks", () => {
42+
it("accepts OK agent output from the captured log when stdout is empty", () => {
43+
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-output-"));
44+
try {
45+
const logPath = join(dir, "agent.log");
46+
writeFileSync(
47+
logPath,
48+
[
49+
"2026-04-24T15:00:00.000Z command stdout",
50+
JSON.stringify({
51+
finalAssistantVisibleText: "OK",
52+
payloads: [{ type: "text", text: "OK" }],
53+
}),
54+
].join("\n"),
55+
);
56+
57+
expect(agentOutputHasExpectedOkMarker("", { logPath })).toBe(true);
58+
} finally {
59+
rmSync(dir, { recursive: true, force: true });
60+
}
61+
});
62+
4063
it("treats explicit empty-string args as values instead of boolean flags", () => {
4164
expect(parseArgs(["--ubuntu-runner", "", "--mode", "both"])).toEqual({
4265
"ubuntu-runner": "",
@@ -122,12 +145,14 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
122145
os_id: "windows",
123146
runner: "blacksmith-32vcpu-windows-2025",
124147
suite: "dev-update",
148+
lane: "upgrade",
125149
}),
126150
);
127151
expect(matrix.include).toContainEqual(
128152
expect.objectContaining({
129153
os_id: "ubuntu",
130154
suite: "installer-fresh",
155+
lane: "fresh",
131156
}),
132157
);
133158
});
@@ -295,7 +320,13 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
295320
await new Promise((resolvePromise) => {
296321
server.close(resolvePromise);
297322
});
298-
expect(await canConnectToLoopbackPort(port)).toBe(false);
323+
for (let attempt = 0; attempt < 20; attempt += 1) {
324+
if (!(await canConnectToLoopbackPort(port, 100))) {
325+
return;
326+
}
327+
await delay(25);
328+
}
329+
expect(await canConnectToLoopbackPort(port, 100)).toBe(false);
299330
});
300331

301332
it("writes Discord smoke config using the strict guild channel schema", () => {

0 commit comments

Comments
 (0)