From cbd666fe60f4c70efb06dee0bf8ae51b473f8a89 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 10:31:11 +0000 Subject: [PATCH] Surface underlying network error instead of generic 'fetch failed' undici's fetch() collapses DNS/TLS/connection-refused/timeout failures into a vague TypeError. Unwrap err.cause so Test Connection shows the real reason (e.g. ECONNREFUSED, certificate error) instead of just 'fetch failed'. --- backend/src/integrations/proxmox.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/src/integrations/proxmox.ts b/backend/src/integrations/proxmox.ts index 8ccaf10..d717697 100644 --- a/backend/src/integrations/proxmox.ts +++ b/backend/src/integrations/proxmox.ts @@ -16,6 +16,17 @@ function authHeader(apiKey: string): Record { // Proxmox ships with a self-signed cert by default, which Node's fetch rejects out of the box. const insecureDispatcher = new Agent({ connect: { rejectUnauthorized: false } }) +// undici's fetch() wraps the real failure (DNS, TLS, connection refused/timed out) in a generic +// "fetch failed" TypeError — unwrap err.cause so the actual reason reaches the UI. +function describeFetchError(err: unknown): string { + if (err instanceof Error) { + const cause = (err as { cause?: unknown }).cause + if (cause instanceof Error) return `${err.message}: ${cause.message}` + return err.message + } + return 'Connection failed' +} + export const proxmox: IntegrationAdapter = { async testConnection(config, secrets) { const baseUrl = config.baseUrl?.replace(/\/$/, '') @@ -30,7 +41,7 @@ export const proxmox: IntegrationAdapter = { if (!res.ok) return { ok: false, message: `HTTP ${res.status}` } return { ok: true, message: 'Connected' } } catch (err) { - return { ok: false, message: err instanceof Error ? err.message : 'Connection failed' } + return { ok: false, message: describeFetchError(err) } } },