From df1452799b28782ebfe273b4dcaf779cf34ced38 Mon Sep 17 00:00:00 2001
From: "Ben.Li"
Date: Sat, 4 Jul 2026 02:24:50 +0800
Subject: [PATCH] fix(android): preserve numeric invoke error codes (#99591)
* fix(android): preserve numeric invoke error codes
* fix(android): tighten invoke error code parsing
---------
Co-authored-by: Peter Steinberger
---
.../openclaw/app/gateway/InvokeErrorParser.kt | 4 +++-
.../app/gateway/InvokeErrorParserTest.kt | 24 +++++++++++++++----
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/gateway/InvokeErrorParser.kt b/apps/android/app/src/main/java/ai/openclaw/app/gateway/InvokeErrorParser.kt
index 5cda3235fb57..fb4fad2e17e9 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/gateway/InvokeErrorParser.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/gateway/InvokeErrorParser.kt
@@ -1,5 +1,7 @@
package ai.openclaw.app.gateway
+private val invokeErrorCodePattern = Regex("^[A-Z][A-Z0-9_]*$")
+
data class ParsedInvokeError(
val code: String,
val message: String,
@@ -24,7 +26,7 @@ fun parseInvokeErrorMessage(raw: String): ParsedInvokeError {
if (parts.size == 2) {
val code = parts[0].trim()
val rest = parts[1].trim()
- if (code.isNotEmpty() && code.all { it.isUpperCase() || it == '_' }) {
+ if (invokeErrorCodePattern.matches(code)) {
return ParsedInvokeError(
code = code,
message = rest.ifEmpty { trimmed },
diff --git a/apps/android/app/src/test/java/ai/openclaw/app/gateway/InvokeErrorParserTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/gateway/InvokeErrorParserTest.kt
index f30cd27ed5ca..3740da8cdb0b 100644
--- a/apps/android/app/src/test/java/ai/openclaw/app/gateway/InvokeErrorParserTest.kt
+++ b/apps/android/app/src/test/java/ai/openclaw/app/gateway/InvokeErrorParserTest.kt
@@ -15,12 +15,28 @@ class InvokeErrorParserTest {
assertEquals("CAMERA_PERMISSION_REQUIRED: grant Camera permission", parsed.prefixedMessage)
}
+ @Test
+ fun parseInvokeErrorMessage_parsesNumericCodePrefix() {
+ val parsed = parseInvokeErrorMessage("A2UI_HOST_UNAVAILABLE: bundled A2UI host not reachable")
+ assertEquals("A2UI_HOST_UNAVAILABLE", parsed.code)
+ assertEquals("bundled A2UI host not reachable", parsed.message)
+ assertTrue(parsed.hadExplicitCode)
+ }
+
@Test
fun parseInvokeErrorMessage_rejectsNonCanonicalCodePrefix() {
- val parsed = parseInvokeErrorMessage("IllegalStateException: boom")
- assertEquals("UNAVAILABLE", parsed.code)
- assertEquals("IllegalStateException: boom", parsed.message)
- assertFalse(parsed.hadExplicitCode)
+ listOf(
+ "IllegalStateException: boom",
+ "2FAST: boom",
+ "_PRIVATE: boom",
+ "CAMERA-PERMISSION: boom",
+ )
+ .forEach { raw ->
+ val parsed = parseInvokeErrorMessage(raw)
+ assertEquals("UNAVAILABLE", parsed.code)
+ assertEquals(raw, parsed.message)
+ assertFalse(parsed.hadExplicitCode)
+ }
}
@Test