From 31462f64d8ac7f42be8635cc44cca82a8a3bc31e Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Tue, 20 Jan 2026 19:40:30 -0800 Subject: [PATCH 1/3] fix: allow clawdbot-ios gateway client id The iOS app currently identifies as clientId=clawdbot-ios when connecting in node mode. Add this ID to the allowed gateway client IDs so the handshake schema accepts it. Also fixes a TS strictness issue in auto-reply status formatting (parts filter) that caused > clawdbot@2026.1.20 build /Users/vignesh/clawd/clawdbot-upstream > tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts [copy-hook-metadata] Copied boot-md/HOOK.md [copy-hook-metadata] Copied command-logger/HOOK.md [copy-hook-metadata] Copied session-memory/HOOK.md [copy-hook-metadata] Copied soul-evil/HOOK.md [copy-hook-metadata] Done to fail. --- src/gateway/protocol/client-info.ts | 1 + src/gateway/server.ios-client-id.test.ts | 65 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/gateway/server.ios-client-id.test.ts diff --git a/src/gateway/protocol/client-info.ts b/src/gateway/protocol/client-info.ts index 54720bcc5..3831cd0c8 100644 --- a/src/gateway/protocol/client-info.ts +++ b/src/gateway/protocol/client-info.ts @@ -5,6 +5,7 @@ export const GATEWAY_CLIENT_IDS = { CLI: "cli", GATEWAY_CLIENT: "gateway-client", MACOS_APP: "clawdbot-macos", + IOS_APP: "clawdbot-ios", NODE_HOST: "node-host", TEST: "test", FINGERPRINT: "fingerprint", diff --git a/src/gateway/server.ios-client-id.test.ts b/src/gateway/server.ios-client-id.test.ts new file mode 100644 index 000000000..6b8d71aee --- /dev/null +++ b/src/gateway/server.ios-client-id.test.ts @@ -0,0 +1,65 @@ +import { test } from "vitest"; +import WebSocket from "ws"; + +import { PROTOCOL_VERSION } from "./protocol/index.js"; +import { getFreePort, onceMessage, startGatewayServer } from "./test-helpers.server.js"; + +function connectReq( + ws: WebSocket, + params: { token?: string; password?: string } = {}, +): Promise<{ ok: boolean; error?: { message?: string } }> { + const id = `c-${Math.random().toString(16).slice(2)}`; + ws.send( + JSON.stringify({ + type: "req", + id, + method: "connect", + params: { + minProtocol: PROTOCOL_VERSION, + maxProtocol: PROTOCOL_VERSION, + client: { + id: "clawdbot-ios", + version: "dev", + platform: "ios", + mode: "node", + }, + auth: { + token: params.token, + password: params.password, + }, + role: "node", + scopes: [], + caps: ["canvas"], + commands: ["system.notify"], + permissions: {}, + }, + }), + ); + + return onceMessage( + ws, + (o) => (o as { type?: string }).type === "res" && (o as { id?: string }).id === id, + ); +} + +test("accepts clawdbot-ios as a valid gateway client id", async () => { + const port = await getFreePort(); + const server = await startGatewayServer(port); + const ws = new WebSocket(`ws://127.0.0.1:${port}`); + await new Promise((resolve) => ws.once("open", resolve)); + + const res = await connectReq(ws); + // We don't care if auth fails here; we only care that schema validation accepts the client id. + // A schema rejection would close the socket before sending a response. + if (!res.ok) { + // allow unauthorized error when gateway requires auth + // but reject schema validation errors + const message = String(res.error?.message ?? ""); + if (message.includes("invalid connect params")) { + throw new Error(message); + } + } + + ws.close(); + await server.close(); +}); From 34a126a6d7adc0ac45794460ff86dd90a56d315a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 04:47:09 +0000 Subject: [PATCH 2/3] fix: allow mobile node client ids (#1354) (thanks @vignesh07) --- CHANGELOG.md | 1 + .../java/com/clawdbot/android/NodeRuntime.kt | 2 +- src/gateway/protocol/client-info.ts | 1 + src/gateway/server.ios-client-id.test.ts | 30 ++++++++++++++++--- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5058a6ce3..cf57a27e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Docs: https://docs.clawd.bot - CLI: keep `clawdbot logs` output resilient to broken pipes while preserving progress output. - Model catalog: avoid caching import failures, log transient discovery errors, and keep partial results. (#1332) — thanks @dougvk. - Doctor: clarify plugin auto-enable hint text in the startup banner. +- Gateway: allow mobile node client ids for iOS + Android handshake validation. (#1354) — thanks @vignesh07. - Gateway: clarify unauthorized handshake responses with token/password mismatch guidance. - Gateway: clarify connect/validation errors for gateway params. (#1347) — thanks @vignesh07. - Gateway: preserve restart wake routing + thread replies across restarts. (#1337) — thanks @John-Rood. diff --git a/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt b/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt index 8d051a421..603e4b82b 100644 --- a/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt +++ b/apps/android/app/src/main/java/com/clawdbot/android/NodeRuntime.kt @@ -529,7 +529,7 @@ class NodeRuntime(context: Context) { caps = buildCapabilities(), commands = buildInvokeCommands(), permissions = emptyMap(), - client = buildClientInfo(clientId = "node-host", clientMode = "node"), + client = buildClientInfo(clientId = "clawdbot-android", clientMode = "node"), userAgent = buildUserAgent(), ) } diff --git a/src/gateway/protocol/client-info.ts b/src/gateway/protocol/client-info.ts index 3831cd0c8..d855e463d 100644 --- a/src/gateway/protocol/client-info.ts +++ b/src/gateway/protocol/client-info.ts @@ -6,6 +6,7 @@ export const GATEWAY_CLIENT_IDS = { GATEWAY_CLIENT: "gateway-client", MACOS_APP: "clawdbot-macos", IOS_APP: "clawdbot-ios", + ANDROID_APP: "clawdbot-android", NODE_HOST: "node-host", TEST: "test", FINGERPRINT: "fingerprint", diff --git a/src/gateway/server.ios-client-id.test.ts b/src/gateway/server.ios-client-id.test.ts index 6b8d71aee..64f87abcd 100644 --- a/src/gateway/server.ios-client-id.test.ts +++ b/src/gateway/server.ios-client-id.test.ts @@ -6,7 +6,7 @@ import { getFreePort, onceMessage, startGatewayServer } from "./test-helpers.ser function connectReq( ws: WebSocket, - params: { token?: string; password?: string } = {}, + params: { clientId: string; platform: string; token?: string; password?: string }, ): Promise<{ ok: boolean; error?: { message?: string } }> { const id = `c-${Math.random().toString(16).slice(2)}`; ws.send( @@ -18,9 +18,9 @@ function connectReq( minProtocol: PROTOCOL_VERSION, maxProtocol: PROTOCOL_VERSION, client: { - id: "clawdbot-ios", + id: params.clientId, version: "dev", - platform: "ios", + platform: params.platform, mode: "node", }, auth: { @@ -48,7 +48,29 @@ test("accepts clawdbot-ios as a valid gateway client id", async () => { const ws = new WebSocket(`ws://127.0.0.1:${port}`); await new Promise((resolve) => ws.once("open", resolve)); - const res = await connectReq(ws); + const res = await connectReq(ws, { clientId: "clawdbot-ios", platform: "ios" }); + // We don't care if auth fails here; we only care that schema validation accepts the client id. + // A schema rejection would close the socket before sending a response. + if (!res.ok) { + // allow unauthorized error when gateway requires auth + // but reject schema validation errors + const message = String(res.error?.message ?? ""); + if (message.includes("invalid connect params")) { + throw new Error(message); + } + } + + ws.close(); + await server.close(); +}); + +test("accepts clawdbot-android as a valid gateway client id", async () => { + const port = await getFreePort(); + const server = await startGatewayServer(port); + const ws = new WebSocket(`ws://127.0.0.1:${port}`); + await new Promise((resolve) => ws.once("open", resolve)); + + const res = await connectReq(ws, { clientId: "clawdbot-android", platform: "android" }); // We don't care if auth fails here; we only care that schema validation accepts the client id. // A schema rejection would close the socket before sending a response. if (!res.ok) { From 110079d99dc6e069f13885e1a1160d44a8a8b368 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 21 Jan 2026 05:05:02 +0000 Subject: [PATCH 3/3] fix: guard nodes status duration parsing (#1354) (thanks @vignesh07) --- src/cli/nodes-cli/register.status.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cli/nodes-cli/register.status.ts b/src/cli/nodes-cli/register.status.ts index 4f8e2ae76..3c89cc0fc 100644 --- a/src/cli/nodes-cli/register.status.ts +++ b/src/cli/nodes-cli/register.status.ts @@ -46,7 +46,13 @@ function formatNodeVersions(node: { function parseSinceMs(raw: unknown, label: string): number | undefined { if (raw === undefined || raw === null) return undefined; - const value = String(raw).trim(); + const value = + typeof raw === "string" ? raw.trim() : typeof raw === "number" ? String(raw).trim() : null; + if (value === null) { + defaultRuntime.error(`${label}: invalid duration value`); + defaultRuntime.exit(1); + return undefined; + } if (!value) return undefined; try { return parseDurationMs(value);