fix(release): stabilize slow live release gates

This commit is contained in:
Peter Steinberger
2026-05-02 16:04:33 +01:00
parent c7b5302acf
commit 3f2c3a69d7
4 changed files with 115 additions and 27 deletions

View File

@@ -147,6 +147,33 @@ function parseUpgradeSurvivorScenarios(raw) {
];
}
function parsePublishedReleaseVersion(spec) {
const match = /^openclaw@([0-9]{4})\.([0-9]+)\.([0-9]+)/u.exec(String(spec ?? ""));
if (!match) {
return null;
}
return {
year: Number(match[1]),
month: Number(match[2]),
day: Number(match[3]),
};
}
function comparePublishedReleaseVersion(a, b) {
return a.year - b.year || a.month - b.month || a.day - b.day;
}
function supportsUpgradeSurvivorPluginDependencyCleanup(baselineSpec) {
if (!baselineSpec) {
return true;
}
const version = parsePublishedReleaseVersion(baselineSpec);
if (!version) {
return true;
}
return comparePublishedReleaseVersion(version, { year: 2026, month: 4, day: 23 }) >= 0;
}
function expandUpgradeSurvivorBaselineLanes(poolLanes, rawBaselineSpecs, rawScenarios = "") {
const baselineSpecs = parseUpgradeSurvivorBaselineSpecs(rawBaselineSpecs);
const scenarios = parseUpgradeSurvivorScenarios(rawScenarios);
@@ -160,30 +187,38 @@ function expandUpgradeSurvivorBaselineLanes(poolLanes, rawBaselineSpecs, rawScen
const matrixBaselines = baselineSpecs.length > 0 ? baselineSpecs : [undefined];
const matrixScenarios = scenarios.length > 0 ? scenarios : [undefined];
return matrixBaselines.flatMap((baselineSpec) =>
matrixScenarios.map((scenario) => {
const suffixParts = [
baselineSpec ? sanitizeLaneNameSuffix(baselineSpec) : "",
scenario && scenario !== "base" ? sanitizeLaneNameSuffix(scenario) : "",
].filter(Boolean);
const suffix = suffixParts.join("-");
const name = suffix ? `${poolLane.name}-${suffix}` : poolLane.name;
const commandPrefix = [
`OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR="$PWD/.artifacts/upgrade-survivor/${name}"`,
baselineSpec ? `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${shellQuote(baselineSpec)}` : "",
scenario ? `OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${shellQuote(scenario)}` : "",
]
.filter(Boolean)
.join(" ");
return Object.assign({}, poolLane, {
cacheKey: poolLane.cacheKey
? suffix
? `${poolLane.cacheKey}-${suffix}`
: poolLane.cacheKey
: name,
command: commandPrefix ? `${commandPrefix} ${poolLane.command}` : poolLane.command,
name,
});
}),
matrixScenarios
.filter(
(scenario) =>
scenario !== "plugin-deps-cleanup" ||
supportsUpgradeSurvivorPluginDependencyCleanup(baselineSpec),
)
.map((scenario) => {
const suffixParts = [
baselineSpec ? sanitizeLaneNameSuffix(baselineSpec) : "",
scenario && scenario !== "base" ? sanitizeLaneNameSuffix(scenario) : "",
].filter(Boolean);
const suffix = suffixParts.join("-");
const name = suffix ? `${poolLane.name}-${suffix}` : poolLane.name;
const commandPrefix = [
`OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR="$PWD/.artifacts/upgrade-survivor/${name}"`,
baselineSpec
? `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${shellQuote(baselineSpec)}`
: "",
scenario ? `OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${shellQuote(scenario)}` : "",
]
.filter(Boolean)
.join(" ");
return Object.assign({}, poolLane, {
cacheKey: poolLane.cacheKey
? suffix
? `${poolLane.cacheKey}-${suffix}`
: poolLane.cacheKey
: name,
command: commandPrefix ? `${commandPrefix} ${poolLane.command}` : poolLane.command,
name,
});
}),
);
});
}

View File

@@ -34,6 +34,11 @@ const SUPPORTED_SUITES = new Set([
"dev-update",
]);
export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = parsePositiveIntegerEnv(
"OPENCLAW_CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS",
1200,
);
const providerConfig = {
openai: {
extensionId: "openai",
@@ -41,7 +46,7 @@ const providerConfig = {
authChoice: "openai-api-key",
model: "openai/gpt-5.4",
baseUrl: "https://api.openai.com/v1",
timeoutSeconds: 600,
timeoutSeconds: CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS,
},
anthropic: {
extensionId: "anthropic",
@@ -112,7 +117,6 @@ export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS =
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000;
export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_000;
export const CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = 600;
export const CROSS_OS_RELEASE_SMOKE_TOOLS_PROFILE = "minimal";
if (isMainModule()) {
@@ -151,6 +155,18 @@ export function parseArgs(argv) {
return parsed;
}
function parsePositiveIntegerEnv(name, fallback) {
const raw = process.env[name]?.trim();
if (!raw) {
return fallback;
}
const value = Number(raw);
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer. Got: ${JSON.stringify(raw)}`);
}
return value;
}
export function looksLikeReleaseVersionRef(ref) {
const trimmed = normalizeRequestedRef(ref);
return /^v?[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[1-9][0-9]*)|[-.](?:beta|rc)[-.]?[0-9]+)?$/iu.test(

View File

@@ -56,12 +56,27 @@ const DEFAULT_MODEL =
// The cron/MCP live probe now tolerates more cancelled tool-call retries in CI,
// so the outer test budget needs enough headroom to finish those retries.
const CLI_BACKEND_LIVE_TIMEOUT_MS = 20 * 60_000;
const CLI_BACKEND_REQUEST_TIMEOUT_MS = 600_000;
const CLI_BACKEND_REQUEST_TIMEOUT_MS = parsePositiveIntegerEnv(
"OPENCLAW_LIVE_CLI_BACKEND_REQUEST_TIMEOUT_MS",
15 * 60_000,
);
const CLI_BACKEND_AGENT_TIMEOUT_SECONDS = Math.max(
1,
Math.ceil(CLI_BACKEND_REQUEST_TIMEOUT_MS / 1000) - 10,
);
function parsePositiveIntegerEnv(name: string, fallback: number): number {
const raw = process.env[name]?.trim();
if (!raw) {
return fallback;
}
const value = Number(raw);
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer. Got: ${JSON.stringify(raw)}`);
}
return value;
}
function logCliBackendLiveStep(step: string, details?: Record<string, unknown>): void {
if (!CLI_DEBUG) {
return;

View File

@@ -358,6 +358,28 @@ describe("scripts/lib/docker-e2e-plan", () => {
]);
});
it("skips plugin dependency cleanup for baselines without packaged plugin dirs", () => {
const plan = planFor({
selectedLaneNames: ["published-upgrade-survivor"],
upgradeSurvivorBaselines: "2026.4.29 2026.3.13",
upgradeSurvivorScenarios: "reported-issues",
});
expect(plan.lanes.map((lane) => lane.name)).toEqual([
"published-upgrade-survivor-2026.4.29",
"published-upgrade-survivor-2026.4.29-feishu-channel",
"published-upgrade-survivor-2026.4.29-bootstrap-persona",
"published-upgrade-survivor-2026.4.29-plugin-deps-cleanup",
"published-upgrade-survivor-2026.4.29-tilde-log-path",
"published-upgrade-survivor-2026.4.29-versioned-runtime-deps",
"published-upgrade-survivor-2026.3.13",
"published-upgrade-survivor-2026.3.13-feishu-channel",
"published-upgrade-survivor-2026.3.13-bootstrap-persona",
"published-upgrade-survivor-2026.3.13-tilde-log-path",
"published-upgrade-survivor-2026.3.13-versioned-runtime-deps",
]);
});
it("expands update migration across baselines and cleanup scenarios", () => {
const plan = planFor({
selectedLaneNames: ["update-migration"],