#10173·moto

CloudFormation: update_stack_instances aborts the whole batch instead of processing each account/region independently

Author: bellmattCreated Aug 10, 2026Updated Aug 11, 2026
Labelsbug

Environment

  • moto 5.2.2
  • boto3 1.43.63

Description

Following up on #10035 / #10041, thanks for adding StackInstanceNotFoundException, note this issue builds upon that, there is no regression.

StackInstances.update() now raises StackInstanceNotFound as soon as it hits the first requested (account, region) target that doesn't have an existing instance:

python
def update(self, accounts, regions, parameters):
    for account in accounts:
        for region in regions:
            instance = self.get_instance(account, region)
            if instance is None:
                raise StackInstanceNotFound()
            instance.parameters = parameters or []

StackInstanceNotFoundException is a documented error for this API (UpdateStackInstances reference), so raising it isn't wrong in isolation. The issue is that raising immediately abandons the rest of the batch, so any targets ordered after the missing one in the Accounts/Regions lists never get updated, even though they do have an existing instance and should have been. The result depends entirely on request-list ordering, which isn't how AWS documents this operation behaving.

See StackSet operation options:

If a stack can't update in the first Region, the update operation continues in that Region, and then moves on to the next Region.

And the documented per-instance status codes (SUCCEEDED, FAILED, PENDING, etc.) model each account/Region's outcome independently within a single operation, not as an all-or-nothing, order-dependent result.

Reproduction

python
import boto3
from moto import mock_aws

@mock_aws
def repro():
    c = boto3.client("cloudformation", region_name="eu-west-1")
    c.create_stack_set(
        StackSetName="demo",
        TemplateBody='{"Resources": {}}',
        PermissionModel="SELF_MANAGED",
        Parameters=[{"ParameterKey": "Foo", "ParameterValue": "original"}],
    )
    # Only accountA (111...) has an instance; accountC (333...) does not.
    c.create_stack_instances(StackSetName="demo", Accounts=["111111111111"], Regions=["eu-west-1"])

    try:
        # accountC is ordered before accountA.
        c.update_stack_instances(
            StackSetName="demo",
            Accounts=["333333333333", "111111111111"],
            Regions=["eu-west-1"],
            ParameterOverrides=[{"ParameterKey": "Foo", "ParameterValue": "updated"}],
        )
    except Exception as e:
        print("raised:", type(e).__name__, e)

    for i in c.list_stack_instances(StackSetName="demo")["Summaries"]:
        print(i["Account"], i["Region"], i.get("ParameterOverrides"))

repro()

Actual behaviour

raised: StackInstanceNotFoundException An error occurred (StackInstanceNotFoundException) when calling the UpdateStackInstances operation: The specified stack instance doesn't exist.
111111111111 eu-west-1 None

accountA's existing instance was never updated (ParameterOverrides stayed None), purely because accountC happened to be listed first.

Expected behaviour

accountA's instance should be updated regardless of the position of accountC in the request, and regardless of whether accountC has an instance yet. StackInstanceNotFoundException seems like the right error for the operation as a whole to surface, but it shouldn't come at the cost of silently dropping updates to valid, existing targets elsewhere in the same request.