From b1b78a06a30dbc5dd54dedf319a544ddbaf1ca38 Mon Sep 17 00:00:00 2001 From: Mavis Date: Tue, 2 Jun 2026 23:47:13 +0000 Subject: [PATCH] fix: Type.code nested-enum bug blocks GetServices decode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real root cause of the 'Cannot read properties of null (reading code)' error was hiding in two places, not one. The first (ConnectionResponse status) was fixed in the previous commit. This commit fixes the second: The KRPC.Type message has a 'code' field of nested-enum type 'Type.TypeCode'. When protobufjs decodes a GetServices response (which contains MANY Type messages inside Procedure.returnType fields), it hits the same nested-enum default-value lookup bug and throws the TypeError. The fix is identical to the ConnectionResponse.status fix: change the field type from the nested-enum reference to plain 'uint32'. The wire format is the same (varint), and our code in services.ts reads raw integers from the typecode field anyway. This error was happening OUTSIDE my top-level try/catch in client.connect() — it was in loadServices, called from adapter.connect(). The bridge's catch caught it, but the error message was the raw 'Cannot read properties of null (reading code)' because loadServices didn't wrap the error. Now: - The schema fix makes the decode actually succeed - The adapter wraps any remaining loadServices errors with a clear 'kRPC loadServices (GetServices decode) failed:' prefix so the next failure mode is immediately actionable --- apps/tools/ksp-bridge/src/krpc-adapter.ts | 13 +++++++++++-- packages/krpc-client/src/schema.ts | 11 ++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/tools/ksp-bridge/src/krpc-adapter.ts b/apps/tools/ksp-bridge/src/krpc-adapter.ts index 4d73f2c..c71ee74 100644 --- a/apps/tools/ksp-bridge/src/krpc-adapter.ts +++ b/apps/tools/ksp-bridge/src/krpc-adapter.ts @@ -64,8 +64,17 @@ export class KRPCAdapter { return; } await this.client.connect(); - const loaded = await loadServices(this.client); - this.services = loaded.services; + try { + const loaded = await loadServices(this.client); + this.services = loaded.services; + } catch (e) { + // loadServices calls client.invoke, which decodes the + // KRPC.GetServices response (a HUGE KRPC.Services message). + // If anything goes wrong decoding that, surface a clear + // error instead of the buried protobufjs TypeError. + const msg = e instanceof Error ? e.message : String(e); + throw new Error(`kRPC loadServices (GetServices decode) failed: ${msg}`); + } } async disconnect(): Promise { diff --git a/packages/krpc-client/src/schema.ts b/packages/krpc-client/src/schema.ts index 9f8773d..5953eb7 100644 --- a/packages/krpc-client/src/schema.ts +++ b/packages/krpc-client/src/schema.ts @@ -204,8 +204,17 @@ const schemaJson = { }, }, Type: { + // The `code` field on Type is a wire-varint enum + // (TypeCode = uint32 under the hood). We model it as a + // plain uint32 to dodge the protobufjs nested-enum + // default-value lookup bug that throws + // "Cannot read properties of null (reading 'code')" + // when decoding a Type message whose code field is + // present. Same fix as ConnectionResponse.status. + // The values stay as a nested TypeCode enum for + // documentation / programmatic lookup (in services.ts). fields: { - code: { type: 'Type.TypeCode', id: 1 }, + code: { type: 'uint32', id: 1 }, service: { type: 'string', id: 2 }, name: { type: 'string', id: 3 }, types: { rule: 'repeated', type: 'Type', id: 4 },