fix: stabilize GPT-5.5 release gates

This commit is contained in:
Peter Steinberger
2026-05-02 06:35:44 +01:00
parent fecac7e40a
commit e052bdcfb6
5 changed files with 70 additions and 15 deletions

View File

@@ -40,6 +40,7 @@ const providerConfig = {
secretEnv: "OPENAI_API_KEY", secretEnv: "OPENAI_API_KEY",
authChoice: "openai-api-key", authChoice: "openai-api-key",
model: "openai/gpt-5.5", model: "openai/gpt-5.5",
timeoutSeconds: 600,
}, },
anthropic: { anthropic: {
extensionId: "anthropic", extensionId: "anthropic",
@@ -91,7 +92,7 @@ export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS =
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000; CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000;
export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_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_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = 360; export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = 600;
if (isMainModule()) { if (isMainModule()) {
try { try {
@@ -1840,6 +1841,22 @@ async function runInstalledModelsSet(params) {
logPath: params.logPath, logPath: params.logPath,
timeoutMs: 2 * 60 * 1000, timeoutMs: 2 * 60 * 1000,
}); });
if (typeof params.providerConfig.timeoutSeconds === "number") {
await runInstalledCli({
cliPath: params.cliPath,
args: [
"config",
"set",
`models.providers.${params.providerConfig.extensionId}.timeoutSeconds`,
String(params.providerConfig.timeoutSeconds),
"--strict-json",
],
cwd: params.cwd,
env: params.env,
logPath: params.logPath,
timeoutMs: 2 * 60 * 1000,
});
}
await runInstalledCli({ await runInstalledCli({
cliPath: params.cliPath, cliPath: params.cliPath,
args: [ args: [
@@ -1875,7 +1892,7 @@ async function runInstalledAgentTurn(params) {
cwd: params.cwd, cwd: params.cwd,
env: params.env, env: params.env,
logPath: params.logPath, logPath: params.logPath,
timeoutMs: 10 * 60 * 1000, timeoutMs: (CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS + 60) * 1000,
}); });
if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) { if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) {
throw new Error("Agent output did not contain the expected OK marker."); throw new Error("Agent output did not contain the expected OK marker.");
@@ -2616,6 +2633,21 @@ async function runModelsSet(params) {
logPath: params.logPath, logPath: params.logPath,
timeoutMs: 2 * 60 * 1000, timeoutMs: 2 * 60 * 1000,
}); });
if (typeof params.providerConfig.timeoutSeconds === "number") {
await runOpenClaw({
lane: params.lane,
env: params.env,
args: [
"config",
"set",
`models.providers.${params.providerConfig.extensionId}.timeoutSeconds`,
String(params.providerConfig.timeoutSeconds),
"--strict-json",
],
logPath: params.logPath,
timeoutMs: 2 * 60 * 1000,
});
}
await runOpenClaw({ await runOpenClaw({
lane: params.lane, lane: params.lane,
env: params.env, env: params.env,
@@ -2648,7 +2680,7 @@ async function runAgentTurn(params) {
env: params.env, env: params.env,
args: buildReleaseAgentTurnArgs(sessionId), args: buildReleaseAgentTurnArgs(sessionId),
logPath: params.logPath, logPath: params.logPath,
timeoutMs: 10 * 60 * 1000, timeoutMs: (CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS + 60) * 1000,
}); });
if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) { if (!agentOutputHasExpectedOkMarker(result.stdout, { logPath: params.logPath })) {
throw new Error("Agent output did not contain the expected OK marker."); throw new Error("Agent output did not contain the expected OK marker.");

View File

@@ -56,7 +56,7 @@ const DEFAULT_MODEL =
// The cron/MCP live probe now tolerates more cancelled tool-call retries in CI, // 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. // so the outer test budget needs enough headroom to finish those retries.
const CLI_BACKEND_LIVE_TIMEOUT_MS = 20 * 60_000; const CLI_BACKEND_LIVE_TIMEOUT_MS = 20 * 60_000;
const CLI_BACKEND_REQUEST_TIMEOUT_MS = 240_000; const CLI_BACKEND_REQUEST_TIMEOUT_MS = 600_000;
const CLI_BACKEND_AGENT_TIMEOUT_SECONDS = Math.max( const CLI_BACKEND_AGENT_TIMEOUT_SECONDS = Math.max(
1, 1,
Math.ceil(CLI_BACKEND_REQUEST_TIMEOUT_MS / 1000) - 10, Math.ceil(CLI_BACKEND_REQUEST_TIMEOUT_MS / 1000) - 10,

View File

@@ -129,15 +129,18 @@ function createManager(options?: {
getRuntimeConfig?: () => Record<string, unknown>; getRuntimeConfig?: () => Record<string, unknown>;
channelIds?: ChannelId[]; channelIds?: ChannelId[];
startupTrace?: { measure: <T>(name: string, run: () => T | Promise<T>) => Promise<T> }; startupTrace?: { measure: <T>(name: string, run: () => T | Promise<T>) => Promise<T> };
fillChannelDependencies?: boolean;
}) { }) {
const log = createSubsystemLogger("gateway/server-channels-test"); const log = createSubsystemLogger("gateway/server-channels-test");
const channelLogs = { discord: log } as Record<ChannelId, SubsystemLogger>; const channelLogs = { discord: log } as Record<ChannelId, SubsystemLogger>;
const runtime = runtimeForLogger(log); const runtime = runtimeForLogger(log);
const channelRuntimeEnvs = { discord: runtime } as unknown as Record<ChannelId, RuntimeEnv>; const channelRuntimeEnvs = { discord: runtime } as unknown as Record<ChannelId, RuntimeEnv>;
const channelIds = options?.channelIds ?? ["discord"]; const channelIds = options?.channelIds ?? ["discord"];
for (const channelId of channelIds) { if (options?.fillChannelDependencies !== false) {
channelLogs[channelId] ??= log.child(channelId); for (const channelId of channelIds) {
channelRuntimeEnvs[channelId] ??= runtime; channelLogs[channelId] ??= log.child(channelId);
channelRuntimeEnvs[channelId] ??= runtime;
}
} }
return createChannelManager({ return createChannelManager({
getRuntimeConfig: () => options?.getRuntimeConfig?.() ?? {}, getRuntimeConfig: () => options?.getRuntimeConfig?.() ?? {},
@@ -576,6 +579,21 @@ describe("server-channels auto restart", () => {
expect(succeedingStart).toHaveBeenCalledTimes(1); expect(succeedingStart).toHaveBeenCalledTimes(1);
}); });
it("uses fallback logger and runtime when a channel is missing startup wiring", async () => {
const startAccount = vi.fn(async () => {
throw new Error("invalid_auth");
});
installTestRegistry(createTestPlugin({ id: "slack", startAccount }));
const manager = createManager({ channelIds: ["slack"], fillChannelDependencies: false });
await manager.startChannels();
await vi.advanceTimersByTimeAsync(0);
expect(startAccount).toHaveBeenCalledTimes(1);
const account = manager.getRuntimeSnapshot().channelAccounts.slack?.[DEFAULT_ACCOUNT_ID];
expect(account?.lastError).toBe("invalid_auth");
});
it("emits startup trace spans for channel preflight and handoff", async () => { it("emits startup trace spans for channel preflight and handoff", async () => {
const measureMock = vi.fn(async (name: string, run: () => unknown) => await run()); const measureMock = vi.fn(async (name: string, run: () => unknown) => await run());
const startupTrace = { const startupTrace = {

View File

@@ -13,7 +13,11 @@ import { type BackoffPolicy, computeBackoff, sleepWithAbort } from "../infra/bac
import { createTaskScopedChannelRuntime } from "../infra/channel-runtime-context.js"; import { createTaskScopedChannelRuntime } from "../infra/channel-runtime-context.js";
import { formatErrorMessage } from "../infra/errors.js"; import { formatErrorMessage } from "../infra/errors.js";
import { resetDirectoryCache } from "../infra/outbound/target-resolver.js"; import { resetDirectoryCache } from "../infra/outbound/target-resolver.js";
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js"; import {
createSubsystemLogger,
runtimeForLogger,
type SubsystemLogger,
} from "../logging/subsystem.js";
import { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js"; import { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js";
import { import {
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
@@ -33,8 +37,6 @@ const CHANNEL_RESTART_POLICY: BackoffPolicy = {
const MAX_RESTART_ATTEMPTS = 10; const MAX_RESTART_ATTEMPTS = 10;
const CHANNEL_STOP_ABORT_TIMEOUT_MS = 5_000; const CHANNEL_STOP_ABORT_TIMEOUT_MS = 5_000;
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
type ChannelRuntimeStore = { type ChannelRuntimeStore = {
aborts: Map<string, AbortController>; aborts: Map<string, AbortController>;
starting: Map<string, Promise<void>>; starting: Map<string, Promise<void>>;
@@ -128,8 +130,8 @@ function applyDescribedAccountFields(
type ChannelManagerOptions = { type ChannelManagerOptions = {
getRuntimeConfig: () => OpenClawConfig; getRuntimeConfig: () => OpenClawConfig;
channelLogs: Record<ChannelId, SubsystemLogger>; channelLogs: Partial<Record<ChannelId, SubsystemLogger>>;
channelRuntimeEnvs: Record<ChannelId, RuntimeEnv>; channelRuntimeEnvs: Partial<Record<ChannelId, RuntimeEnv>>;
/** /**
* Optional channel runtime helpers for external channel plugins. * Optional channel runtime helpers for external channel plugins.
* *
@@ -706,7 +708,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
try { try {
await measureStartup(`channels.${plugin.id}.start`, () => startChannel(plugin.id)); await measureStartup(`channels.${plugin.id}.start`, () => startChannel(plugin.id));
} catch (err) { } catch (err) {
channelLogs[plugin.id]?.error?.( ensureChannelLog(plugin.id).error?.(
`[${plugin.id}] channel startup failed: ${formatErrorMessage(err)}`, `[${plugin.id}] channel startup failed: ${formatErrorMessage(err)}`,
); );
} }

View File

@@ -160,11 +160,14 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
expect(allowlist).not.toContain("web-readability"); expect(allowlist).not.toContain("web-readability");
}); });
it("keeps cross-OS live smoke agent turns on minimal thinking", () => { it("keeps cross-OS live smoke agent turns on GPT-5.5-safe timeouts and minimal thinking", () => {
const source = readFileSync("scripts/openclaw-cross-os-release-checks.ts", "utf8"); const source = readFileSync("scripts/openclaw-cross-os-release-checks.ts", "utf8");
expect(source).toContain('"--thinking",\n "minimal"'); expect(source).toContain('"--thinking",\n "minimal"');
expect(CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS).toBeLessThanOrEqual(360); expect(CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS).toBeGreaterThanOrEqual(600);
expect(source).toContain(
"models.providers.${params.providerConfig.extensionId}.timeoutSeconds",
);
expect(source).toContain('"--timeout",\n String(CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS)'); expect(source).toContain('"--timeout",\n String(CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS)');
expect(source.match(/buildReleaseAgentTurnArgs\(sessionId\)/g)?.length).toBeGreaterThanOrEqual( expect(source.match(/buildReleaseAgentTurnArgs\(sessionId\)/g)?.length).toBeGreaterThanOrEqual(
2, 2,