From f65a23d092349321de2dd56864bf152b2a3f620e Mon Sep 17 00:00:00 2001 From: cpacker Date: Sat, 28 Feb 2026 15:56:32 -0800 Subject: [PATCH] fix(remote): handle non-JSON responses from registration endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `letta remote` connects before the cloud API is running (e.g. ngrok returning HTML error pages), the `.json()` call on the response threw an unhandled SyntaxError causing a hard exit with "Failed to parse JSON". Now both register and re-register paths catch JSON parse failures and surface actionable error messages indicating the server may not be running. 🐛 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta --- src/cli/commands/listen.ts | 53 ++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/cli/commands/listen.ts b/src/cli/commands/listen.ts index 9e3bdf2..6582981 100644 --- a/src/cli/commands/listen.ts +++ b/src/cli/commands/listen.ts @@ -237,14 +237,27 @@ export async function handleListen( }); if (!registerResponse.ok) { - const error = (await registerResponse.json()) as { message?: string }; - throw new Error(error.message || "Registration failed"); + let errorMessage = `Registration failed (HTTP ${registerResponse.status})`; + try { + const error = (await registerResponse.json()) as { message?: string }; + if (error.message) errorMessage = error.message; + } catch { + // Response body is not JSON (e.g. HTML error page from proxy) + const text = await registerResponse.text().catch(() => ""); + if (text) errorMessage += `: ${text.slice(0, 200)}`; + } + throw new Error(errorMessage); } - const { connectionId, wsUrl } = (await registerResponse.json()) as { - connectionId: string; - wsUrl: string; - }; + let registerBody: { connectionId: string; wsUrl: string }; + try { + registerBody = (await registerResponse.json()) as typeof registerBody; + } catch { + throw new Error( + "Registration endpoint returned non-JSON response — is the server running?", + ); + } + const { connectionId, wsUrl } = registerBody; updateCommandResult( ctx.buffersRef, @@ -354,16 +367,28 @@ export async function handleListen( }); if (!reregisterResponse.ok) { - const error = (await reregisterResponse.json()) as { - message?: string; - }; - throw new Error(error.message || "Re-registration failed"); + let errorMessage = `Re-registration failed (HTTP ${reregisterResponse.status})`; + try { + const error = (await reregisterResponse.json()) as { + message?: string; + }; + if (error.message) errorMessage = error.message; + } catch { + const text = await reregisterResponse.text().catch(() => ""); + if (text) errorMessage += `: ${text.slice(0, 200)}`; + } + throw new Error(errorMessage); } - const reregisterData = (await reregisterResponse.json()) as { - connectionId: string; - wsUrl: string; - }; + let reregisterData: { connectionId: string; wsUrl: string }; + try { + reregisterData = + (await reregisterResponse.json()) as typeof reregisterData; + } catch { + throw new Error( + "Re-registration endpoint returned non-JSON response — is the server running?", + ); + } // Restart client with new connectionId await startClient(