diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a37dfe581c..43aff8bd18c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ Docs: https://docs.openclaw.ai - Slack/startup: harden `@slack/bolt` import interop across current bundled runtime shapes so Slack monitors no longer crash with `App is not a constructor` after plugin-sdk bundling changes. (#45953) Thanks @merc1305. - Windows/gateway status: accept `schtasks` `Last Result` output as an alias for `Last Run Result`, so running scheduled-task installs no longer show `Runtime: unknown`. (#47844) Thanks @MoerAI. - ACP/acpx: resolve the bundled plugin root from the actual plugin directory so plugin-local installs stay under `dist/extensions/acpx` instead of escaping to `dist/extensions` and failing runtime setup. (#47601) Thanks @ngutman. +- Gateway/WS handshake: raise the default pre-auth handshake timeout to 10 seconds and add `OPENCLAW_HANDSHAKE_TIMEOUT_MS` as a runtime override so busy local gateways stop dropping healthy CLI connections at 3 seconds. (#49262) Thanks @fuller-stack-dev. - Gateway/websocket pairing bypass for disabled auth: skip device-pairing enforcement for Control UI operator sessions when `gateway.auth.mode=none`, so reverse-proxied dashboards no longer get stuck on `pairing required` despite auth being explicitly disabled. (#47148) Thanks @ademczuk. - Control UI/model switching: preserve the selected provider prefix when switching models from the chat dropdown, so multi-provider setups no longer send `anthropic/gpt-5.2`-style mismatches when the user picked `openai/gpt-5.2`. (#47581) Thanks @chrishham. - Control UI/storage: scope persisted settings keys by gateway base path, with migration from the legacy shared key, so multiple gateways under one domain stop overwriting each other's dashboard preferences. (#47932) Thanks @bobBot-claw. diff --git a/src/gateway/server-constants.ts b/src/gateway/server-constants.ts index 036ebc5b3fa..54dc3f794b6 100644 --- a/src/gateway/server-constants.ts +++ b/src/gateway/server-constants.ts @@ -21,10 +21,14 @@ export const __setMaxChatHistoryMessagesBytesForTest = (value?: number) => { maxChatHistoryMessagesBytes = value; } }; -export const DEFAULT_HANDSHAKE_TIMEOUT_MS = 3_000; +export const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000; export const getHandshakeTimeoutMs = () => { - if (process.env.VITEST && process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS) { - const parsed = Number(process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS); + // User-facing env var (works in all environments); test-only var gated behind VITEST + const envKey = + process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || + (process.env.VITEST && process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS); + if (envKey) { + const parsed = Number(envKey); if (Number.isFinite(parsed) && parsed > 0) { return parsed; } diff --git a/src/gateway/server.auth.default-token.suite.ts b/src/gateway/server.auth.default-token.suite.ts index 4d090b78cb3..ed15150a029 100644 --- a/src/gateway/server.auth.default-token.suite.ts +++ b/src/gateway/server.auth.default-token.suite.ts @@ -93,6 +93,29 @@ export function registerDefaultAuthTokenSuite(): void { } }); + test("prefers OPENCLAW_HANDSHAKE_TIMEOUT_MS and falls back on empty string", () => { + const prevHandshakeTimeout = process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS; + const prevTestHandshakeTimeout = process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS; + process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS = "75"; + process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS = "20"; + try { + expect(getHandshakeTimeoutMs()).toBe(75); + process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS = ""; + expect(getHandshakeTimeoutMs()).toBe(20); + } finally { + if (prevHandshakeTimeout === undefined) { + delete process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS; + } else { + process.env.OPENCLAW_HANDSHAKE_TIMEOUT_MS = prevHandshakeTimeout; + } + if (prevTestHandshakeTimeout === undefined) { + delete process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS; + } else { + process.env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS = prevTestHandshakeTimeout; + } + } + }); + test("connect (req) handshake returns hello-ok payload", async () => { const { STATE_DIR, createConfigIO } = await import("../config/config.js"); const ws = await openWs(port);