diff --git a/src/cli/tui/screens/deploy/useDeployFlow.ts b/src/cli/tui/screens/deploy/useDeployFlow.ts index 8b0295744..4ff82aca9 100644 --- a/src/cli/tui/screens/deploy/useDeployFlow.ts +++ b/src/cli/tui/screens/deploy/useDeployFlow.ts @@ -12,6 +12,7 @@ import { parsePolicyEngineOutputs, parsePolicyOutputs, } from '../../../cloudformation'; +import { checkStackStatus } from '../../../cloudformation/stack-status'; import { DEFAULT_DEPLOY_ATTRS, computeDeployAttrs } from '../../../commands/deploy/utils.js'; import { getErrorMessage, isChangesetInProgressError, isExpiredTokenError } from '../../../errors'; import { ExecLogger } from '../../../logging'; @@ -612,27 +613,47 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState setIsDiffLoading(true); setPreDeployDiffStep(prev => ({ ...prev, status: 'running' })); logger.startStep('Computing diff changes...'); - switchableIoHost?.setOnRawMessage((code, _level, message, data) => { - logger.logDiff(code, message); - if (code === 'CDK_TOOLKIT_I4002') { - setDiffSummaries(prev => [...prev, parseStackDiff(data, message)]); - } else if (code === 'CDK_TOOLKIT_I4001') { - setNumStacksWithChanges(parseDiffResult(data).numStacksWithChanges); - } - }); - switchableIoHost?.setVerbose(true); + + // Skip diff for new stacks — there's nothing to compare against, and CDK's diff + // creates a temporary changeset that leaves a ghost stack which races with deploy. + let skipDiff = false; try { - await cdkToolkitWrapper.diff(); + const target = context?.awsTargets[0]; + const currentStackName = stackNames?.[0]; + if (target && currentStackName) { + const status = await checkStackStatus(target.region, currentStackName); + skipDiff = !status.exists; + } } catch { - // Diff failure is non-fatal — deploy will proceed - } finally { - switchableIoHost?.setVerbose(false); - switchableIoHost?.setOnRawMessage(null); - isDiffRunningRef.current = false; - setIsDiffLoading(false); - logger.endStep('success'); - setPreDeployDiffStep(prev => ({ ...prev, status: 'success' })); + // Status check failed — fall through to diff (tolerate old race) } + + if (skipDiff) { + logger.log('New stack — skipping diff (nothing to compare against)'); + } else { + switchableIoHost?.setOnRawMessage((code, _level, message, data) => { + logger.logDiff(code, message); + if (code === 'CDK_TOOLKIT_I4002') { + setDiffSummaries(prev => [...prev, parseStackDiff(data, message)]); + } else if (code === 'CDK_TOOLKIT_I4001') { + setNumStacksWithChanges(parseDiffResult(data).numStacksWithChanges); + } + }); + switchableIoHost?.setVerbose(true); + try { + await cdkToolkitWrapper.diff(); + } catch { + // Diff failure is non-fatal — deploy will proceed + } finally { + switchableIoHost?.setVerbose(false); + switchableIoHost?.setOnRawMessage(null); + } + } + + isDiffRunningRef.current = false; + setIsDiffLoading(false); + logger.endStep('success'); + setPreDeployDiffStep(prev => ({ ...prev, status: 'success' })); } setPublishAssetsStep(prev => ({ ...prev, status: 'running' }));