From e2810d02d7d223bc67288c3e445a738bb73a04e9 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Jul 2026 19:12:08 +0000 Subject: [PATCH] fix: expose argo operation timing --- .agents/skills/unidesk-cicd/references/branch-follower.md | 2 ++ scripts/native/cicd/compact-native-object.mjs | 8 +++++++- scripts/native/cicd/read-state-summary.mjs | 3 +++ scripts/src/cicd-debug.ts | 3 +++ scripts/src/cicd.ts | 5 ++++- 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.agents/skills/unidesk-cicd/references/branch-follower.md b/.agents/skills/unidesk-cicd/references/branch-follower.md index 6ea4378c..c3e602e5 100644 --- a/.agents/skills/unidesk-cicd/references/branch-follower.md +++ b/.agents/skills/unidesk-cicd/references/branch-follower.md @@ -48,6 +48,8 @@ Follower-scoped commands such as `status --follower`, `events --follower`, `logs 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. +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. Stage timing rows must not label optional gates as `not-ready` when they are not part of that follower's closeout contract. For sentinel-like followers without a GitOps branch flush gate, git-mirror source snapshot readiness should render as source-ready/ready, while missing GitOps `githubInSync` remains `-`/not-applicable instead of a failure-looking state. diff --git a/scripts/native/cicd/compact-native-object.mjs b/scripts/native/cicd/compact-native-object.mjs index bdab2aeb..b021c86f 100644 --- a/scripts/native/cicd/compact-native-object.mjs +++ b/scripts/native/cicd/compact-native-object.mjs @@ -115,7 +115,13 @@ if (key === "pipelineRun") { : [], nonReadyResources, operationState: input?.status?.operationState - ? { phase: input.status.operationState.phase || null, message: input.status.operationState.message || null, finishedAt: input.status.operationState.finishedAt || null } + ? { + phase: input.status.operationState.phase || null, + message: input.status.operationState.message || null, + startedAt: input.status.operationState.startedAt || null, + finishedAt: input.status.operationState.finishedAt || null, + durationSeconds: durationSeconds(input.status.operationState.startedAt, input.status.operationState.finishedAt), + } : null, }, }; diff --git a/scripts/native/cicd/read-state-summary.mjs b/scripts/native/cicd/read-state-summary.mjs index 51f7a048..a3b07051 100644 --- a/scripts/native/cicd/read-state-summary.mjs +++ b/scripts/native/cicd/read-state-summary.mjs @@ -194,6 +194,9 @@ function compactArgo(argo) { revision: stringOrNull(value.revision), operationPhase: stringOrNull(value.operationPhase), operationMessage: stringOrNull(value.operationMessage), + operationStartedAt: stringOrNull(value.operationStartedAt), + operationFinishedAt: stringOrNull(value.operationFinishedAt), + operationDurationSeconds: numberOrNull(value.operationDurationSeconds), conditions: arrayRecords(value.conditions).slice(0, 5), nonReadyResources: arrayRecords(value.nonReadyResources).slice(0, 5), ready: value.ready === true, diff --git a/scripts/src/cicd-debug.ts b/scripts/src/cicd-debug.ts index d5eca3c5..66866f24 100644 --- a/scripts/src/cicd-debug.ts +++ b/scripts/src/cicd-debug.ts @@ -319,6 +319,9 @@ function compactStatusGates(payload: Record | null): Record | null): Record< revision: stringOrNull(sync?.revision), operationPhase: stringOrNull(operationState?.phase), operationMessage: stringOrNull(operationState?.message), + operationStartedAt: stringOrNull(operationState?.startedAt), + operationFinishedAt: stringOrNull(operationState?.finishedAt), + operationDurationSeconds: numberOrNull(operationState?.durationSeconds), conditions: Array.isArray(status?.conditions) ? status.conditions.slice(0, 5) : [], nonReadyResources: Array.isArray(status?.nonReadyResources) ? status.nonReadyResources.slice(0, 5) : [], ready: argoApplicationReady(application), @@ -2423,7 +2426,7 @@ function stageTimingsFromNativePayload(payload: Record | null): } const argo = asOptionalRecord(payload.argo); if (argo !== null) { - stages.push(stageTiming("argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, null, null, "argocd", stringOrNull(argo.name))); + stages.push(stageTiming("argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, numberOrNull(argo.operationDurationSeconds), null, "argocd", stringOrNull(argo.name))); } const runtime = asOptionalRecord(payload.runtime); if (runtime !== null) {