Skip to content

gen2-migration refactor: two defects — stale REVIEW_IN_PROGRESS holding stack blocks re-runs, and plan phase leaves zero-change change sets #14988

Description

@aaronjwhipple87

Before opening, please complete the following steps:

Describe the bug

Found while migrating a small Gen 1 app (auth + storage + api + 2 functions, us-west-2) with amplify gen2-migration 14.5.1. The migration itself completed on 2026-09-21 once the CloudFormation ExecuteStackRefactor service issue was fixed; these two are tool defects that surfaced when the refactor step had to be re-run.

Defect 1: a failed refactor leaves a REVIEW_IN_PROGRESS holding stack that blocks every later run, and the automatic rollback then dies on it

When ExecuteStackRefactor fails on the first move (Gen 2 placeholder auth → <gen2-auth-stack>-holding), the refactor's CREATE STACK action has already created the holding stack in REVIEW_IN_PROGRESS with an empty template.

  • Re-running refactor then fails immediately because the tool treats the existing -holding stack as a conflict, and it cannot proceed past it.
  • The tool's automatic rollback tries to update/delete that shell and fails with Unable to find … / an empty-template error, so the run ends with a second failure on top of the first.
  • Workaround we used: delete the REVIEW_IN_PROGRESS shell by hand (aws cloudformation delete-stack), or pre-create the holding stacks (CREATE_COMPLETE, one AWS::CloudFormation::WaitConditionHandle) — the tool accepts pre-created holding stacks and then emits only MOVE actions.

Expected: the tool should detect a REVIEW_IN_PROGRESS holding stack from an earlier attempt and either delete/recreate it or reuse it, and the rollback path should tolerate an empty-template shell.

Defect 2: the plan phase leaves zero-change change sets on stacks it decides to skip

For stacks whose "prepare" step produces no template change (in our case the storage stacks on runs after the first), the plan phase still creates a change set, CloudFormation reports it as having no changes, and the tool leaves it in place (FAILED / "No updates are to be performed"). Several accumulate across retries and have to be deleted by hand.

Expected: delete the change set when it reports no changes (or skip creating it after a dry-run describe).

Suggestion: run DetectStackDrift as a pre-flight

While diagnosing the CloudFormation refusal we found the Gen 2 placeholder auth stack drifted (a hand edit to the placeholder app client) and the Gen 1 auth stack drifted (identity pool missing stack tags). Drift turned out not to be the trigger, but a moved resource with drift is a real risk for the refactor. A DetectStackDrift on every source/destination nested stack before the first CreateStackRefactor, failing fast on DRIFTED, would be a cheap safeguard.

Reproduction steps

  1. Gen 1 app locked; Gen 2 branch deployed with ampx pipeline-deploy; amplify gen2-migration refactor --to <gen2-root>.
  2. Make the first ExecuteStackRefactor fail (we hit the CloudFormation service issue, now fixed; any execute failure reproduces the shell).
  3. Observe <gen2-auth-stack>-holding in REVIEW_IN_PROGRESS; observe the rollback error.
  4. Re-run the same command → rejected because of the holding stack.
  5. Observe leftover change sets on the storage stacks (aws cloudformation list-change-sets).

Environment

  • @aws-amplify/cli 14.5.1, @aws-amplify/backend 1.18.x, Node 22, macOS
  • Region us-west-2, account 170201464775 (details in the two support cases above)

Additional post-refactor findings (already passed to the service team via case 178914507800209, listed for completeness)

  • Generated auth/resource.ts omits UserPoolName / IdentityPoolName; after refactor the adopted pool has explicit names and UserPoolName is replacement-class, so postRefactor() needs to pin them (the generator already does this for the storage bucket name).
  • An existing bucket policy on the adopted bucket makes the Step 8 deploy fail on the AWS::S3::BucketPolicy re-create ("bucket policy already exists").
  • The Gen 2 storage stack's Custom::S3AutoDeleteObjects re-points at the adopted production bucket; the generated Retain override is the only guard.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions