From 720aa75d78ad9a3b7752d7c02573435e67d9c129 Mon Sep 17 00:00:00 2001 From: Theodore Song Date: Wed, 19 Aug 2026 08:31:01 -0400 Subject: [PATCH] Classify nested Neon connection failures --- api/state.js | 12 +++++++++--- scripts/test-server-state.mjs | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/api/state.js b/api/state.js index 3c49157..402bf9d 100644 --- a/api/state.js +++ b/api/state.js @@ -19,8 +19,9 @@ function withBlobAuth(options = {}) { export function providerErrorCode(error) { if (!error) return "not_configured_or_not_attempted"; - const code = String(error.code || error.cause && error.cause.code || "").toUpperCase(); - const message = String(error.message || error).toLowerCase(); + const nested = error.sourceError || error.cause; + const code = String(error.code || nested && (nested.code || nested.cause && nested.cause.code) || "").toUpperCase(); + const message = `${String(error.message || error)} ${String(nested && nested.message || "")}`.toLowerCase(); if (code === "28P01" || code === "28000") return "authorization_failed"; if (code === "3D000") return "database_not_found"; if (code.startsWith("08")) return "connection_failed"; @@ -35,12 +36,17 @@ export function providerErrorCode(error) { } function providerErrorMetadata(error) { + const nested = error && (error.sourceError || error.cause); const name = String(error && error.name || "Error"); - const code = String(error && (error.code || error.cause && error.cause.code) || "").toUpperCase(); + const nestedName = String(nested && nested.name || "none"); + const code = String(error && (error.code || nested && (nested.code || nested.cause && nested.cause.code)) || "").toUpperCase(); + const httpStatus = String(error && error.message || "").match(/HTTP status\s+(\d{3})/i); return { status: providerErrorCode(error), error_class: /^[A-Za-z][A-Za-z0-9_]{0,39}$/.test(name) ? name : "Error", + source_error_class: /^[A-Za-z][A-Za-z0-9_]{0,39}$/.test(nestedName) ? nestedName : "none", error_code: /^[A-Z0-9_]{1,32}$/.test(code) ? code : "none", + http_status: httpStatus ? Number(httpStatus[1]) : null, }; } diff --git a/scripts/test-server-state.mjs b/scripts/test-server-state.mjs index e578ffc..e6dba47 100644 --- a/scripts/test-server-state.mjs +++ b/scripts/test-server-state.mjs @@ -47,5 +47,10 @@ assert.equal(compactedState.signal_ledger.expired_ungraded, 7); assert.equal(providerErrorCode(new Error("403 Forbidden")), "authorization_failed"); assert.equal(providerErrorCode(new Error("invalid connection string")), "invalid_connection_string"); assert.equal(providerErrorCode(Object.assign(new Error("database rejected login"), { code: "28P01" })), "authorization_failed"); +const nestedFetchError = Object.assign(new Error("Error connecting to database"), { + name: "NeonDbError", + sourceError: Object.assign(new TypeError("fetch failed"), { cause: { code: "ENOTFOUND" } }), +}); +assert.equal(providerErrorCode(nestedFetchError), "dns_failed"); console.log("server state tests passed");