Files
openclaw/src/gateway/handshake-timeouts.ts
Brad Groux 6e94b047e2 fix: improve WS handshake reliability on slow-startup environments (#60075)
* fix: import CHANNEL_IDS from leaf module to avoid TDZ on init (#48832)

schema.ts and validation.ts imported CHANNEL_IDS from channels/registry.js,
which re-exports from channels/ids.js but also imports plugins/runtime.js.
When the bundler resolves this dependency graph, the re-exported CHANNEL_IDS
can be undefined at the point config/validation.ts evaluates (temporal dead
zone), causing 'CHANNEL_IDS is not iterable' on startup.

Fix: import CHANNEL_IDS directly from channels/ids.js (the leaf module with
zero heavy dependencies) and normalizeChatChannelId from channels/chat-meta.js.

Fixes #48832

* fix: improve WS handshake reliability on slow-startup environments (#48736)

On Windows with large dist bundles (46MB/639 files), heavy synchronous
module loading blocks the event loop during CLI startup, preventing
timely processing of the connect.challenge frame and causing ~80%
handshake timeout failures.

Changes:
- Yield event loop (setImmediate) before starting WS connection in
  callGateway to let pending I/O drain after heavy module loading
- Add OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS env var override for
  client-side connect challenge timeout (server already has
  OPENCLAW_HANDSHAKE_TIMEOUT_MS)
- Include diagnostic timing in challenge timeout error messages
  (elapsed vs limit) for easier debugging
- Add tests for env var override and resolution logic

---------

Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
2026-04-03 00:21:14 -05:00

47 lines
1.5 KiB
TypeScript

export const DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 10_000;
export const MIN_CONNECT_CHALLENGE_TIMEOUT_MS = 250;
export const MAX_CONNECT_CHALLENGE_TIMEOUT_MS = DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
export function clampConnectChallengeTimeoutMs(timeoutMs: number): number {
return Math.max(
MIN_CONNECT_CHALLENGE_TIMEOUT_MS,
Math.min(MAX_CONNECT_CHALLENGE_TIMEOUT_MS, timeoutMs),
);
}
export function getConnectChallengeTimeoutMsFromEnv(
env: NodeJS.ProcessEnv = process.env,
): number | undefined {
const raw = env.OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS;
if (raw) {
const parsed = Number(raw);
if (Number.isFinite(parsed) && parsed > 0) {
return parsed;
}
}
return undefined;
}
export function resolveConnectChallengeTimeoutMs(timeoutMs?: number | null): number {
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
return clampConnectChallengeTimeoutMs(timeoutMs);
}
const envOverride = getConnectChallengeTimeoutMsFromEnv();
if (envOverride !== undefined) {
return clampConnectChallengeTimeoutMs(envOverride);
}
return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
}
export function getPreauthHandshakeTimeoutMsFromEnv(env: NodeJS.ProcessEnv = process.env): number {
const configuredTimeout =
env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || (env.VITEST && env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS);
if (configuredTimeout) {
const parsed = Number(configuredTimeout);
if (Number.isFinite(parsed) && parsed > 0) {
return parsed;
}
}
return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
}