From 41a5315168f1e94e954b8285e32416ac6f2f97ca Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 23:38:45 +0000 Subject: [PATCH] fix(cicd): preserve taskrun drilldown read errors --- scripts/native/cicd/taskrun-drilldown.mjs | 49 +++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/scripts/native/cicd/taskrun-drilldown.mjs b/scripts/native/cicd/taskrun-drilldown.mjs index aef56764..9cb9abf4 100644 --- a/scripts/native/cicd/taskrun-drilldown.mjs +++ b/scripts/native/cicd/taskrun-drilldown.mjs @@ -15,6 +15,17 @@ const useServiceAccount = Boolean(process.env.KUBERNETES_SERVICE_HOST && process && existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token") && existsSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); +class KubeReadError extends Error { + constructor(reason, message, details = {}) { + super(shortText(message) || reason); + this.name = "KubeReadError"; + this.reason = reason; + this.statusCode = details.statusCode ?? null; + this.notFound = details.notFound === true; + this.path = details.path || null; + } +} + main().catch((error) => { process.stderr.write(error?.message || String(error)); process.exit(1); @@ -218,21 +229,32 @@ async function readPodLog(podName, container, tailLines, limitBytes) { } async function getJson(path, required) { + let text = ""; + try { + text = await getText(path, required); + } catch (error) { + if (!required && isNotFoundError(error)) return null; + throw error; + } try { - const text = await getText(path, required); return JSON.parse(text); } catch (error) { - if (required) throw error; - return null; + throw new KubeReadError("invalid-json", `kube api returned non-JSON for ${path}: ${error?.message || String(error)}`, { path }); } } async function getText(path, required) { if (useServiceAccount) return kubeApiGet(path, required); const result = spawnSync("kubectl", ["get", "--raw", path], { encoding: "utf8", maxBuffer: Math.max(maxLogBytes * 2, 1024 * 1024) }); + if (result.error) { + throw new KubeReadError("transport-error", `kubectl get --raw transport error for ${path}: ${result.error.message}`, { path }); + } if (result.status === 0) return result.stdout; - if (!required && /not found|404|NotFound/u.test(result.stderr || result.stdout)) return ""; - throw new Error(shortText(result.stderr || result.stdout || `kubectl get --raw failed with exit ${result.status}`)); + const body = result.stderr || result.stdout || `kubectl get --raw failed with exit ${result.status}`; + throw new KubeReadError(isNotFoundText(body) ? "not-found" : "kube-api-error", `kubectl get --raw failed for ${path}: ${body}`, { + path, + notFound: isNotFoundText(body), + }); } function kubeApiGet(path, required) { @@ -248,15 +270,26 @@ function kubeApiGet(path, required) { res.on("end", () => { const code = res.statusCode || 0; if (code >= 200 && code < 300) resolve(body); - else if (!required && code === 404) resolve(""); - else reject(new Error(shortText(body || `kube api status ${code}`))); + else reject(new KubeReadError(code === 404 || isNotFoundText(body) ? "not-found" : "kube-api-error", `kube api GET ${path} status ${code}: ${body || "-"}`, { + path, + statusCode: code, + notFound: code === 404 || isNotFoundText(body), + })); }); }); - req.on("error", reject); + req.on("error", (error) => reject(new KubeReadError("transport-error", `kube api GET ${path} transport error: ${error?.message || String(error)}`, { path }))); req.end(); }); } +function isNotFoundError(error) { + return error instanceof KubeReadError && error.notFound === true; +} + +function isNotFoundText(value) { + return /\b404\b|not found|NotFound/u.test(String(value || "")); +} + function compactTerminated(value) { if (!value) return null; return {