From 190d4b929246264cd58bf339b75cf6dc06193987 Mon Sep 17 00:00:00 2001 From: Jefferson Ramos Date: Wed, 17 Jun 2026 14:08:55 -0300 Subject: [PATCH] fix: make approveStage idempotent to prevent duplicate Analysis stage When an ApprovalPolicy auto-approves the Analysis stage, calling approveStage('Analysis') again would fail with a duplicate error. Move the guard into the hook itself so all callers are protected. Co-Authored-By: Claude Opus 4.6 --- src/components/update-plan/DecisionActions.tsx | 12 +++++++++--- src/hooks/useApprovalActions.ts | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/update-plan/DecisionActions.tsx b/src/components/update-plan/DecisionActions.tsx index 58be3ef..59fa111 100644 --- a/src/components/update-plan/DecisionActions.tsx +++ b/src/components/update-plan/DecisionActions.tsx @@ -44,8 +44,7 @@ const DecisionActions: React.FC = ({ proposal, clusterVers [approvals, proposal.metadata?.name, proposal.metadata?.namespace], ); - const { approveStage, denyStage, error, clearError, inProgress } = - useApprovalActions(approval); + const { approveStage, denyStage, error, clearError, inProgress } = useApprovalActions(approval); const [confirmingApprove, setConfirmingApprove] = React.useState(false); const [confirmingDeny, setConfirmingDeny] = React.useState(false); @@ -109,7 +108,14 @@ const DecisionActions: React.FC = ({ proposal, clusterVers setConfirmingApprove(false); }, CONFIRM_TIMEOUT_MS); } - }, [confirmingApprove, approveStage, proposal, clusterVersion, history, t]); + }, [ + confirmingApprove, + approveStage, + proposal, + clusterVersion, + history, + t, + ]); const handleDenyClick = React.useCallback(async () => { if (confirmingDeny) { diff --git a/src/hooks/useApprovalActions.ts b/src/hooks/useApprovalActions.ts index 980e56a..0d47431 100644 --- a/src/hooks/useApprovalActions.ts +++ b/src/hooks/useApprovalActions.ts @@ -23,6 +23,8 @@ export const useApprovalActions = (approval?: LightspeedProposalApproval) => { setError(null); try { const existingStages = currentApproval.spec?.stages ?? []; + if (existingStages.some((s) => s.type === stageType)) return true; // already approved + const stageEntry: Record = { type: stageType, decision: 'Approved',