diff --git a/scripts/e2e/kitchen-sink-rpc-walk.mjs b/scripts/e2e/kitchen-sink-rpc-walk.mjs index 628f766df5d0..346b706adce2 100644 --- a/scripts/e2e/kitchen-sink-rpc-walk.mjs +++ b/scripts/e2e/kitchen-sink-rpc-walk.mjs @@ -563,7 +563,10 @@ export function parseGatewayCliRequestFailure(error) { } catch { return null; } - const requestError = payload?.ok === false ? payload.error : null; + return payload?.ok === false ? createGatewayClientRequestError(payload.error) : null; +} + +export function createGatewayClientRequestError(requestError) { if ( requestError?.type !== "gateway_request_error" || !isNonEmptyString(requestError.code) || @@ -712,6 +715,10 @@ function hasOwnPayloadField(raw, field) { export function unwrapRpcPayload(raw) { if (raw?.ok === false) { + const requestError = createGatewayClientRequestError(raw.error); + if (requestError) { + throw requestError; + } throw new Error(`gateway RPC failed: ${boundedJsonPreview(raw.error ?? raw)}`); } if ( diff --git a/test/scripts/kitchen-sink-rpc-walk.test.ts b/test/scripts/kitchen-sink-rpc-walk.test.ts index eb6b19ec868b..6066bc2cf9cb 100644 --- a/test/scripts/kitchen-sink-rpc-walk.test.ts +++ b/test/scripts/kitchen-sink-rpc-walk.test.ts @@ -860,6 +860,31 @@ describe("kitchen-sink RPC payload unwrapping", () => { }); }); + it("preserves gateway request error metadata from built RPC calls", () => { + const error = captureSyncError(() => + unwrapRpcPayload({ + ok: false, + error: { + type: "gateway_request_error", + code: "INVALID_REQUEST", + message: "unauthorized role: operator", + details: { method: "skills.bins" }, + retryable: false, + retryAfterMs: 250, + }, + }), + ); + + expect(error).toMatchObject({ + name: "GatewayClientRequestError", + message: "unauthorized role: operator", + gatewayCode: "INVALID_REQUEST", + details: { method: "skills.bins" }, + retryable: false, + retryAfterMs: 250, + }); + }); + it("bounds failed RPC payload diagnostics", () => { const error = captureSyncError(() => unwrapRpcPayload({ @@ -983,6 +1008,19 @@ describe("kitchen-sink RPC command catalog assertions", () => { }); }), ).resolves.toBeUndefined(); + await expect( + assertOperatorRpcDenied({ method: "skills.bins", params: {} }, async () => + unwrapRpcPayload({ + ok: false, + error: { + type: "gateway_request_error", + code: "INVALID_REQUEST", + message: "unauthorized role: operator", + retryable: false, + }, + }), + ), + ).resolves.toBeUndefined(); await expect( assertOperatorRpcDenied({ method: "skills.bins", params: {} }, async () => { throw new Error(