From 301ee9cdd8ba71dabc19ef9ba99587ee6a5c629f Mon Sep 17 00:00:00 2001 From: HonzysClawdbot Date: Mon, 16 Mar 2026 05:55:56 +0100 Subject: [PATCH] fix(gateway): probe /api/health instead of root URL (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(gateway): probe /api/health instead of root URL for health checks (#390) The server-side gateway health probe was fetching the root URL (/) which returns HTTP 400 on OpenClaw 2026.3.13 gateways. The gateway exposes a dedicated /api/health endpoint that returns 200 with status info. The WebSocket ping RPC 'unknown method' issue is already handled — websocket.ts detects the INVALID_REQUEST and falls back to passive heartbeat mode. The actual bug was this HTTP probe hitting the wrong endpoint. Fixes #390 * fix(gateway): ensure gateways table exists before health probe The gateways table is created lazily by the gateways API (ensureTable). The health route was querying it directly without CREATE IF NOT EXISTS, causing SqliteError: no such table: gateways in fresh databases (E2E tests, Docker first-boot). Add ensureGatewaysTable() inline to mirror the pattern in route.ts. * fix: update health-utils test to match /api/health probe path The test file has its own copy of buildGatewayProbeUrl — update it to append /api/health instead of / to match the route.ts change. --------- Co-authored-by: Nyk <0xnykcd@googlemail.com> --- src/app/api/gateways/health/health-utils.test.ts | 6 +++--- src/app/api/gateways/health/route.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/api/gateways/health/health-utils.test.ts b/src/app/api/gateways/health/health-utils.test.ts index c1e330a..d7d079e 100644 --- a/src/app/api/gateways/health/health-utils.test.ts +++ b/src/app/api/gateways/health/health-utils.test.ts @@ -80,7 +80,7 @@ function buildGatewayProbeUrl(host: string, port: number): string | null { if (!parsed.port && Number.isFinite(port) && port > 0) { parsed.port = String(port) } - if (!parsed.pathname) parsed.pathname = '/' + parsed.pathname = parsed.pathname.replace(/\/+$/, '') + '/api/health' return parsed.toString() } catch { return null @@ -88,7 +88,7 @@ function buildGatewayProbeUrl(host: string, port: number): string | null { } if (!Number.isFinite(port) || port <= 0) return null - return `http://${rawHost}:${port}/` + return `http://${rawHost}:${port}/api/health` } function parseGatewayVersion(headers: Record): string | null { @@ -180,7 +180,7 @@ describe('isBlockedUrl', () => { describe('buildGatewayProbeUrl', () => { it('builds URL from bare host + port', () => { - expect(buildGatewayProbeUrl('example.com', 8080)).toBe('http://example.com:8080/') + expect(buildGatewayProbeUrl('example.com', 8080)).toBe('http://example.com:8080/api/health') }) it('preserves https:// protocol', () => { diff --git a/src/app/api/gateways/health/route.ts b/src/app/api/gateways/health/route.ts index 3f58d33..381637d 100644 --- a/src/app/api/gateways/health/route.ts +++ b/src/app/api/gateways/health/route.ts @@ -144,7 +144,7 @@ function buildGatewayProbeUrl(host: string, port: number): string | null { if (!parsed.port && Number.isFinite(port) && port > 0) { parsed.port = String(port) } - if (!parsed.pathname) parsed.pathname = '/' + parsed.pathname = parsed.pathname.replace(/\/+$/, '') + '/api/health' return parsed.toString() } catch { return null @@ -152,7 +152,7 @@ function buildGatewayProbeUrl(host: string, port: number): string | null { } if (!Number.isFinite(port) || port <= 0) return null - return `http://${rawHost}:${port}/` + return `http://${rawHost}:${port}/api/health` } /**