Skip to content

Commit 4aca412

Browse files
grypezclaude
andcommitted
fix(kernel-node-runtime): propagate vat rejection messages in RPC error responses
Vat rejections reach the RPC layer as serialised CapData objects ({ body, slots }) rather than Error instances. Extract the error message from the smallcaps body so callers see the original rejection string instead of a generic 'Internal error'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b6dc2be commit 4aca412

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

packages/kernel-node-runtime/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Vat rejection messages now propagate through the RPC socket server. Previously, vat rejections arrived at the RPC layer as serialised CapData objects and were silently swallowed, returning a generic `'Internal error'` to callers. The error message is now correctly extracted and returned.
13+
1014
## [0.1.0]
1115

1216
### Added

packages/kernel-node-runtime/src/daemon/rpc-socket-server.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { RpcService } from '@metamask/kernel-rpc-methods';
22
import type { KernelDatabase } from '@metamask/kernel-store';
33
import type { Kernel } from '@metamask/ocap-kernel';
4+
import { kunser } from '@metamask/ocap-kernel';
45
import { rpcHandlers } from '@metamask/ocap-kernel/rpc';
56
import { unlink } from 'node:fs/promises';
67
import { createServer } from 'node:net';
@@ -186,12 +187,47 @@ async function processRequest(
186187
return { jsonrpc: '2.0', id, result: result ?? null };
187188
} catch (error) {
188189
const code = isRpcError(error) ? error.code : -32603;
189-
const message = error instanceof Error ? error.message : 'Internal error';
190+
const message = extractErrorMessage(error);
190191

191192
return { jsonrpc: '2.0', id, error: { code, message } };
192193
}
193194
}
194195

196+
/**
197+
* Extract a human-readable message from an unknown thrown value.
198+
*
199+
* Handles two cases:
200+
* - `Error` instances: use `error.message` directly.
201+
* - CapData objects: vat rejections propagate as serialised CapData; deserialise
202+
* with `kunser` and extract the message if the result is an Error.
203+
* - Everything else: fall back to `'Internal error'`.
204+
*
205+
* @param error - The caught value.
206+
* @returns A string suitable for the JSON-RPC error `message` field.
207+
*/
208+
function extractErrorMessage(error: unknown): string {
209+
if (error instanceof Error) {
210+
return error.message;
211+
}
212+
// Vat rejections arrive as CapData objects: { body: string, slots: unknown[] }
213+
if (
214+
typeof error === 'object' &&
215+
error !== null &&
216+
'body' in error &&
217+
'slots' in error
218+
) {
219+
try {
220+
const deserialized = kunser(error as Parameters<typeof kunser>[0]);
221+
if (deserialized instanceof Error) {
222+
return deserialized.message;
223+
}
224+
} catch {
225+
// Fall through to default
226+
}
227+
}
228+
return 'Internal error';
229+
}
230+
195231
/**
196232
* Check if an error is an RPC error with a numeric code.
197233
*

0 commit comments

Comments
 (0)