paperclipai/paperclip · error · Error

gateway not connected

Error message

gateway not connected

What it means

Thrown by GatewayClient.request (openclaw-gateway) when a JSON-RPC request frame is attempted but the underlying WebSocket is missing or not in the OPEN state. The client requires connect() to complete (WS open + challenge + connect handshake) before any request(), so this guards against use-before-connect and use-after-close.

Source

Thrown at packages/adapters/openclaw-gateway/src/server/execute.ts:712

    );

    const nonce = await withTimeout(this.challengePromise, timeoutMs, "gateway connect challenge timeout");
    const signedConnectParams = buildConnectParams(nonce);

    const hello = await this.request<Record<string, unknown> | null>("connect", signedConnectParams, {
      timeoutMs,
    });

    return hello;
  }

  async request<T>(
    method: string,
    params: unknown,
    opts: GatewayClientRequestOptions,
  ): Promise<T> {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      throw new Error("gateway not connected");
    }

    const id = randomUUID();
    const frame: GatewayRequestFrame = {
      type: "req",
      id,
      method,
      params,
    };

    const payload = JSON.stringify(frame);
    const requestPromise = new Promise<T>((resolve, reject) => {
      const timer =
        opts.timeoutMs > 0
          ? setTimeout(() => {
              this.pending.delete(id);
              reject(new Error(`gateway request timeout (${method})`));
            }, opts.timeoutMs)

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Always await client.connect(buildConnectParams, timeoutMs) before issuing any request().
  2. Register for the WS close/error events (or wrap the client) and reconnect before retrying the request.
  3. Guard the call site: check the client is connected, or implement a reconnect-then-request wrapper with a short timeout.
  4. Avoid calling close() concurrently with in-flight requests; drain pending requests first.

Example fix

// before
const client = new GatewayClient(opts);
await client.request("run", params, { timeoutMs: 5000 }); // throws: not connected
// after
await client.connect(buildConnectParams, 5000);
await client.request("run", params, { timeoutMs: 5000 });
Defensive patterns

Strategy: try-catch

Validate before calling

function isGatewayReady(client: { ws: WebSocket | null }): boolean {
  return client.ws != null && client.ws.readyState === WebSocket.OPEN;
}
// guard before request
if (!isGatewayReady(client)) { await client.connect(buildConnectParams, 5000); }

Type guard

function isGatewayConnected(client: { ws: WebSocket | null }): client is { ws: WebSocket & { readyState: typeof WebSocket.OPEN } } {
  return client.ws != null && client.ws.readyState === WebSocket.OPEN;
}

Try / catch

async function requestWithReconnect<T>(client: GatewayClient, method: string, params: unknown, opts: GatewayClientRequestOptions): Promise<T> {
  try {
    return await client.request<T>(method, params, opts);
  } catch (e) {
    if (e instanceof Error && /gateway not connected/.test(e.message)) {
      await client.connect(buildConnectParams, opts.timeoutMs);
      return client.request<T>(method, params, opts);
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: request() is called before connect() resolves; request() is called after the WS emitted close/error (this.ws set to null by close() or never reassigned); a race where the server closed the socket between the readyState check and ws.send().

Common situations: Caller forgets to await connect(); the gateway dropped the connection (network blip, server restart) and the caller did not register a close handler to reconnect; concurrent close() racing a request().

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/28426cf11969fe5f. Report an issue: GitHub.