From 12186e14a9160fc96977af9ee4a59763eaad49a1 Mon Sep 17 00:00:00 2001 From: Cash Williams Date: Sat, 3 Jan 2026 13:12:05 -0600 Subject: [PATCH] fix(android): handle unreachable gateway gracefully Previously, if the gateway was unreachable (wrong IP, offline, etc.), the Android app would crash with an unhandled socket exception. Changes: - Wrap socket.connect() in try/catch to handle connection failures - Return PairResult with error message instead of crashing - Display actual error message in status text instead of generic 'pairing required' This gives users useful feedback like 'Connection refused' or 'Network is unreachable' instead of a crash. --- .../java/com/clawdis/android/NodeRuntime.kt | 3 +- .../android/bridge/BridgePairingClient.kt | 31 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/android/app/src/main/java/com/clawdis/android/NodeRuntime.kt b/apps/android/app/src/main/java/com/clawdis/android/NodeRuntime.kt index e19951495..7744bacf2 100644 --- a/apps/android/app/src/main/java/com/clawdis/android/NodeRuntime.kt +++ b/apps/android/app/src/main/java/com/clawdis/android/NodeRuntime.kt @@ -433,7 +433,8 @@ class NodeRuntime(context: Context) { } if (!resolved.ok || resolved.token.isNullOrBlank()) { - _statusText.value = "Failed: pairing required" + val errorMessage = resolved.error?.trim().orEmpty().ifEmpty { "pairing required" } + _statusText.value = "Failed: $errorMessage" return@launch } diff --git a/apps/android/app/src/main/java/com/clawdis/android/bridge/BridgePairingClient.kt b/apps/android/app/src/main/java/com/clawdis/android/bridge/BridgePairingClient.kt index 355b13614..6b2c79201 100644 --- a/apps/android/app/src/main/java/com/clawdis/android/bridge/BridgePairingClient.kt +++ b/apps/android/app/src/main/java/com/clawdis/android/bridge/BridgePairingClient.kt @@ -37,21 +37,21 @@ class BridgePairingClient { withContext(Dispatchers.IO) { val socket = Socket() socket.tcpNoDelay = true - socket.connect(InetSocketAddress(endpoint.host, endpoint.port), 8_000) - socket.soTimeout = 60_000 - - val reader = BufferedReader(InputStreamReader(socket.getInputStream(), Charsets.UTF_8)) - val writer = BufferedWriter(OutputStreamWriter(socket.getOutputStream(), Charsets.UTF_8)) - - fun send(line: String) { - writer.write(line) - writer.write("\n") - writer.flush() - } - - fun sendJson(obj: JsonObject) = send(obj.toString()) - try { + socket.connect(InetSocketAddress(endpoint.host, endpoint.port), 8_000) + socket.soTimeout = 60_000 + + val reader = BufferedReader(InputStreamReader(socket.getInputStream(), Charsets.UTF_8)) + val writer = BufferedWriter(OutputStreamWriter(socket.getOutputStream(), Charsets.UTF_8)) + + fun send(line: String) { + writer.write(line) + writer.write("\n") + writer.flush() + } + + fun sendJson(obj: JsonObject) = send(obj.toString()) + sendJson( buildJsonObject { put("type", JsonPrimitive("hello")) @@ -111,6 +111,9 @@ class BridgePairingClient { } else -> PairResult(ok = false, token = null, error = "unexpected bridge response") } + } catch (e: Exception) { + val message = e.message?.trim().orEmpty().ifEmpty { "gateway unreachable" } + PairResult(ok = false, token = null, error = message) } finally { try { socket.close()