From fd203138bf22924258f0161f76302d4ee968bd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 29 Jul 2026 21:35:40 +0000 Subject: [PATCH] client: tolerate empty response bodies on 2xx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every verb ended in an unconditional res.json(), which throws "Unexpected end of JSON input" on 201/204 responses that carry no body — so a request that actually succeeded still surfaced as an error (e.g. a sent chat message toasting a failure). Read text first and only parse when non-empty, otherwise resolve undefined. Non-empty JSON is unchanged. Co-Authored-By: Claude Opus 4.8 --- src/workspaces/hooks/src/useClient.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/workspaces/hooks/src/useClient.ts b/src/workspaces/hooks/src/useClient.ts index ddd18f8e..df766d54 100644 --- a/src/workspaces/hooks/src/useClient.ts +++ b/src/workspaces/hooks/src/useClient.ts @@ -53,6 +53,13 @@ export const getHeaders = (isText: boolean = false) => { return headers; }; +// 2xx responses can legitimately carry no body (201 Created / 204 No Content). res.json() throws on an +// empty body, so read text first and only parse when there's something — otherwise resolve undefined. +const parseBody = async (res: Response): Promise => { + const text = await res.text(); + return (text ? JSON.parse(text) : undefined) as T; +}; + export const getText = async (uri: string, baseUrl = '') => { const headers = getHeaders(true); const theUrl = baseUrl ? `${baseUrl}/${uri.replace(/^\//, '')}` : uri; @@ -67,8 +74,7 @@ export const get = async (uri: string, baseUrl = '') => { const theUrl = baseUrl ? `${baseUrl}/${uri.replace(/^\//, '')}` : uri; const res = await fetch(theUrl, { headers }); await validateResponse(res); - const data = await res.json(); - return data as T; + return parseBody(res); }; export const getBlob = async (uri: string, baseUrl = '') => { @@ -100,8 +106,7 @@ export const post = async (uri: string, payload?: any, baseUrl = '') => { headers, }); await validateResponse(res); - const data = await res.json(); - return data as T; + return parseBody(res); }; export const put = async (uri: string, payload?: any, baseUrl = '') => { @@ -119,8 +124,7 @@ export const put = async (uri: string, payload?: any, baseUrl = '') => { headers, }); await validateResponse(res); - const data = await res.json(); - return data as T; + return parseBody(res); }; export const patch = async (uri: string, payload?: any, baseUrl = '') => { @@ -132,8 +136,7 @@ export const patch = async (uri: string, payload?: any, baseUrl = '') => { headers, }); await validateResponse(res); - const data = await res.json(); - return data as T; + return parseBody(res); }; export const DELETE = async (uri: string, payload?: any, baseUrl = '') => { @@ -145,8 +148,7 @@ export const DELETE = async (uri: string, payload?: any, baseUrl = '') => { headers, }); await validateResponse(res); - const data = await res.json(); - return data as T; + return parseBody(res); }; const validateResponse = async (res: Response) => {