Skip to content

cli: cdk import fails on stacks containing non-ASCII characters (GetTemplate mangling) #1915

Description

@adriantaut

Describe the bug

cdk import fails on any stack whose deployed template contains a non-ASCII character in a resource property, even when the local code is byte-identical to what is actually deployed.

Root cause: cdk import builds its import template from the deployed template (CloudFormation GetTemplate) plus the resources being imported. GetTemplate is lossy — it replaces non-ASCII characters with a literal ASCII ?. CDK therefore submits a template in which an untouched resource's property differs from what CloudFormation actually holds, and CloudFormation rejects the IMPORT change set:

You have modified resources [<LogicalId>] in your template that are not being imported.
Update, create or delete operations cannot be executed during import operations.

The named resource has not been modified by the user at all — the only difference is — (U+2014) vs ?.

This is the same CloudFormation mangling already known to cdk diff, which has masked it since aws/aws-cdk#25912 ("Omitted N changes because they are likely mangled non-ASCII characters. Use --strict to print them."). That masking was never applied to the cdk import code path, so the problem is invisible in cdk diff and then blocks cdk import with an error that points at an innocent resource.

The affected property in our case is AWS::IAM::ManagedPolicy.Description, which is create-only — so it cannot be "fixed" in place to work around the issue. Changing it forces a replacement, and since ManagedPolicyName is also create-only and fixed, replacement fails with EntityAlreadyExists. Affected users have no remediation path within CDK.

Expected Behavior

cdk import succeeds. A property whose deployed value differs from the local value only by GetTemplate's non-ASCII mangling should be treated as unchanged — the same way cdk diff has treated it since aws/aws-cdk#25912.

Current Behavior

cdk import fails during change-set creation:

MyFoundationStack: creating CloudFormation changeset...

 ❌  MyFoundationStack failed: CloudFormationServiceException [ValidationError]:
 You have modified resources [MyScopedPolicy1A2B3C4D] in your template that are not
 being imported. Update, create or delete operations cannot be executed during import operations.

Verification that the resource is genuinely unmodified:

  • Local synthesized template: "Description": "Scoped IAM access for the app production role — App resources only." (U+2014)
  • aws iam get-policy: returns the same string, with U+2014 intact — the live resource is correct
  • aws cloudformation get-template: returns "Scoped IAM access for the app production role ? App resources only." (?, 0x3F)
  • cdk diff reports no change to this resource, printing only Omitted 1 changes because they are likely mangled non-ASCII characters.

Editing the local source to contain a literal ? does not help — the published import template is byte-identical either way, since the local source is not an input to it. That is what pinned the cause to the GetTemplate round-trip.

Reproduction Steps

  1. Synthesize a stack containing a resource with a non-ASCII character in a string property, e.g.:

    new iam.ManagedPolicy(this, 'Policy', {
      managedPolicyName: 'my-policy',
      description: 'Scoped access — resources only.',   // U+2014 EM DASH
      statements: [ /* ... */ ],
    });
  2. cdk deploy.

  3. aws cloudformation get-template --stack-name <stack> — the description comes back with ? instead of —.

  4. Add to the same stack a resource that already exists in the account and is intended for import (any importable type; we used an AWS::SNS::Topic plus its AWS::SNS::Subscription).

  5. cdk import <stack> and supply the physical identifier when prompted.

  6. Change-set creation fails with the ValidationError above, naming the policy — a resource not involved in the import.

Possible Solution

Apply the mangling-aware comparison introduced in aws/aws-cdk#25912 to the import path: when building the import template, treat a deployed value that is the mangled form of the local value as unchanged. Alternatively, construct the import template from the synthesized template (which retains the true characters) rather than from GetTemplate output.

Workaround for anyone hitting this: bypass cdk import and submit the change set directly, using the synthesized template as the base, so the true characters reach CloudFormation.

aws s3 cp import-template.json s3://<bucket>/import-template.json
aws cloudformation create-change-set --stack-name <stack> \
  --change-set-name import-x --change-set-type IMPORT \
  --capabilities CAPABILITY_NAMED_IAM \
  --template-url https://<bucket>.s3.<region>.amazonaws.com/import-template.json \
  --resources-to-import '[{"ResourceType":"AWS::SNS::Topic","LogicalResourceId":"...","ResourceIdentifier":{"TopicArn":"..."}}]'
aws cloudformation execute-change-set --stack-name <stack> --change-set-name import-x

This succeeded immediately on the same stack where cdk import failed, with the change set containing only the two intended Import actions — confirming the deployed state was never actually divergent. Imported resources need an explicit DeletionPolicy in that template.

Additional Information/Context

The underlying GetTemplate behaviour is a CloudFormation API limitation, reported and still open:

Related CDK history:

Since CloudFormation is unlikely to fix GetTemplate soon, mirroring aws/aws-cdk#25912's approach in the import path seems the practical fix. Filing this because the diff side was mitigated in 2023 while import still fails, and the error message gives no indication that character encoding is involved — it names an unrelated resource, which makes it very hard to diagnose.

AWS CDK Library version (aws-cdk-lib)

2.267.0

AWS CDK CLI version

2.1139.0 (build d51353e)

Node.js Version

v24.5.0

OS

macOS 26.5.2 (arm64)

Language

TypeScript

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

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions