From 63987a83289ab08a893159bd196b0b1205bc9e9d Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 19:22:58 +0000 Subject: [PATCH] fix: expose tekton taskrun failures --- .../references/branch-follower.md | 2 ++ scripts/native/cicd/read-state-summary.mjs | 14 ++++++++ scripts/src/cicd-debug.ts | 33 +++++++++++++++++++ scripts/src/cicd-drilldown-render.ts | 19 +++++++++++ 4 files changed, 68 insertions(+) diff --git a/.agents/skills/unidesk-cicd/references/branch-follower.md b/.agents/skills/unidesk-cicd/references/branch-follower.md index 476dce5d..c8e08eba 100644 --- a/.agents/skills/unidesk-cicd/references/branch-follower.md +++ b/.agents/skills/unidesk-cicd/references/branch-follower.md @@ -50,6 +50,8 @@ Multi-follower status summaries should omit per-follower `command.payload`/nativ Argo closeout visibility must include the bounded reason for non-ready health, not only `Synced/Progressing`: health message, operation phase/message, short Application conditions and a small list of non-healthy resources when available. +Tekton failure visibility must include bounded TaskRun detail, not only PipelineRun `Failed`: failed TaskRuns, active TaskRuns and slow TaskRuns with task name, reason and duration. Without this, performance/failure work cannot move past the PipelineRun gate. + When Argo exposes operation start/finish timestamps, stage timing rows should report the Argo operation duration directly. Missing timestamps still render `-`; do not infer Argo duration from total elapsed time or from unrelated runtime polling. The automatic controller loop is non-blocking, so closeout acceleration cannot live only in the user-facing `--wait` path. Once a triggered PipelineRun has succeeded and required runtime/GitOps gates are not aligned, the in-cluster controller path should perform the same bounded target-side Argo refresh used by wait closeout; otherwise convergence depends on Argo's background poll interval and can exceed the 120s budget even when Tekton finished quickly. diff --git a/scripts/native/cicd/read-state-summary.mjs b/scripts/native/cicd/read-state-summary.mjs index 99981a9d..c104091f 100644 --- a/scripts/native/cicd/read-state-summary.mjs +++ b/scripts/native/cicd/read-state-summary.mjs @@ -219,6 +219,7 @@ function compactRuntime(runtime) { function compactTaskRuns(taskRuns) { const value = recordOrNull(taskRuns); if (value === null) return null; + const items = arrayRecords(value.items); return { ok: value.ok === true, count: numberOrNull(value.count), @@ -226,6 +227,19 @@ function compactTaskRuns(taskRuns) { failedCount: numberOrNull(value.failedCount), activeCount: numberOrNull(value.activeCount), performance: recordOrNull(value.performance), + failedItems: items.filter((item) => item.status === "False").slice(0, 5).map(compactTaskRunItem), + activeItems: items.filter((item) => item.status !== "True" && item.status !== "False").slice(0, 5).map(compactTaskRunItem), + slowItems: arrayRecords(recordOrNull(value.performance)?.slowTaskRuns).slice(0, 5).map(compactTaskRunItem), + }; +} + +function compactTaskRunItem(item) { + return { + name: stringOrNull(item.name), + pipelineTask: stringOrNull(item.pipelineTask), + status: stringOrNull(item.status), + reason: stringOrNull(item.reason), + durationSeconds: numberOrNull(item.durationSeconds), }; } diff --git a/scripts/src/cicd-debug.ts b/scripts/src/cicd-debug.ts index 66866f24..b963c63f 100644 --- a/scripts/src/cicd-debug.ts +++ b/scripts/src/cicd-debug.ts @@ -291,6 +291,7 @@ function compactStatusGates(payload: Record | null): Record | null): Record | null): Record, mode: "failed" | "active" | "slow"): Record[] { + const explicit = mode === "failed" ? taskRuns.failedItems : mode === "active" ? taskRuns.activeItems : taskRuns.slowItems; + const explicitItems = arrayRecords(explicit); + if (explicitItems.length > 0) return explicitItems.slice(0, 5).map(compactTaskRunItem); + const items = arrayRecords(taskRuns.items); + if (mode === "failed") return items.filter((item) => item.status === "False").slice(0, 5).map(compactTaskRunItem); + if (mode === "active") return items.filter((item) => item.status !== "True" && item.status !== "False").slice(0, 5).map(compactTaskRunItem); + return arrayRecords(asOptionalRecord(taskRuns.performance)?.slowTaskRuns).slice(0, 5).map(compactTaskRunItem); +} + +function compactTaskRunItem(item: Record): Record { + return { + name: stringOrNull(item.name), + pipelineTask: stringOrNull(item.pipelineTask), + status: stringOrNull(item.status), + reason: stringOrNull(item.reason), + durationSeconds: numberOrNull(item.durationSeconds), + }; +} + +function arrayRecords(value: unknown): Record[] { + return Array.isArray(value) ? value.filter((item): item is Record => typeof item === "object" && item !== null && !Array.isArray(item)) : []; +} + function compactFollowerDecision(state: FollowerState): Record { return { phase: state.phase, diff --git a/scripts/src/cicd-drilldown-render.ts b/scripts/src/cicd-drilldown-render.ts index 466ca0cb..e011aecd 100644 --- a/scripts/src/cicd-drilldown-render.ts +++ b/scripts/src/cicd-drilldown-render.ts @@ -45,6 +45,19 @@ function nativeGateRows(native: Record | null): unknown[][] { const duration = numberOrNull(tekton.durationSeconds); rows.push(["tekton", status, duration === null ? stringOrNull(tekton.reason) ?? "-" : `${duration}s`, stringOrNull(tekton.name) ?? "-"]); } + const taskRuns = asOptionalRecord(native.taskRuns); + if (taskRuns !== null) { + const failed = arrayRecords(taskRuns.failedItems)[0]; + const active = arrayRecords(taskRuns.activeItems)[0]; + const slow = arrayRecords(taskRuns.slowItems)[0]; + const detail = taskRunDetail(failed ?? active ?? slow); + const status = numberOrNull(taskRuns.failedCount) !== null && numberOrNull(taskRuns.failedCount)! > 0 + ? `failed:${numberOrNull(taskRuns.failedCount)}` + : numberOrNull(taskRuns.activeCount) !== null && numberOrNull(taskRuns.activeCount)! > 0 + ? `active:${numberOrNull(taskRuns.activeCount)}` + : "ok"; + rows.push(["taskruns", status, detail, "-"]); + } const argo = asOptionalRecord(native.argo); if (argo !== null) { rows.push(["argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, argoDetail(argo), stringOrNull(argo.name) ?? "-"]); @@ -58,6 +71,12 @@ function nativeGateRows(native: Record | null): unknown[][] { return rows; } +function taskRunDetail(item: Record | undefined): string { + if (item === undefined) return "-"; + const duration = numberOrNull(item.durationSeconds); + return `${item.pipelineTask ?? item.name ?? "task"} ${item.reason ?? item.status ?? "-"}${duration === null ? "" : ` ${duration}s`}`; +} + function argoDetail(argo: Record): string { const resource = arrayRecords(argo.nonReadyResources)[0]; const condition = arrayRecords(argo.conditions)[0];