aws / aws/aws-cdk

aws-lambda: currentVersion logical ID non-deterministic due to unsorted VPC SubnetIds and non-deterministic calculateLayersHash for imported layers

Open
#38,348 2 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-lambda bug effort/medium p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

calculateFunctionHash includes VpcConfig in the version hash (VERSION_LOCKED map marks VpcConfig: true). When a Lambda is placed in a VPC resolved via Vpc.fromLookup() and cdk.context.json is not persisted between synthesis runs, each cdk synth performs a fresh AWS DescribeSubnets call which does not guarantee a stable ordering of results. Different subnet ordering in VpcConfig.SubnetIds produces a different md5 hash, generating a new AWS::Lambda::Version logical ID even when no Lambda code or configuration has actually changed.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

fn.currentVersion should produce a stable logical ID across cdk synth runs when no version-affecting properties have actually changed. Subnet ordering in VpcConfig does not affect Lambda execution semantics and should not influence the version hash.

### Current Behavior

Deploying a stack with zero changes to the Lambda function fails with:

Resource handler returned message: "A version for this Lambda function exists (3). Modify the function to create a new version."
(Service: Lambda, Status Code: 409, Request ID: ...)

CloudFormation attempts to create a new AWS::Lambda::Version resource (because the logical ID changed) but PublishVersion returns the existing version since the code SHA is unchanged, causing a 409 conflict.

CDK diff output showing the issue (no code/config changes between runs):

```
Resources
[-] AWS::Lambda::Version TeradataProxy/CurrentVersion TeradataProxyCurrentVersionE14E6432ce3ded426383c925d02b17b9fa3fba90 destroy
[+] AWS::Lambda::Version TeradataProxy/CurrentVersion TeradataProxyCurrentVersionE14E64323af0853082f50587830dc5e8e166cb89
[~] AWS::Lambda::Alias TeradataProxyLive TeradataProxyLiveD2A48820
└─ [~] FunctionVersion
└─ [~] .Fn::GetAtt:
└─ @@ -1,4 +1,4 @@
[ ] [
[-] "TeradataProxyCurrentVersionE14E6432ce3ded426383c925d02b17b9fa3fba90",
[+] "TeradataProxyCurrentVersionE14E64323af0853082f50587830dc5e8e166cb89",
[ ] "Version"
[ ] ]

```

### Reproduction Steps

```
import { Stack, StackProps, Duration } from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Vpc } from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";

export class ReproStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);

const vpc = Vpc.fromLookup(this, "Vpc", { vpcId: "vpc-xxxxxxxx" });

const fn = new lambda.Function(this, "Fn", {
code: lambda.Code.fromAsset("lambda"),
handler: "index.handler",
runtime: lambda.Runtime.PYTHON_3_12,
vpc,
timeout: Duration.seconds(30),
});

new lambda.Alias(this, "Live", {
aliasName: "live",
version: fn.currentVersion,
provisionedConcurrentExecutions: 2,
});
}
}
```

Steps:
1. Deploy the stack successfully (Version resource created with logical ID suffix aabbccdd)
2. Delete cdk.context.json (simulates a fresh CI checkout where the file is gitignored)
3. Run cdk synth again with zero code changes
4. Observe that the Version logical ID suffix has changed (e.g. eeff0011)
5. cdk deploy fails with 409 AlreadyExists

This is reproducible in CI environments where cdk.context.json is not committed to version control (which is the default CDK .gitignore template).

### Possible Solution

Sort VpcConfig.SubnetIds and VpcConfig.SecurityGroupIds arrays before hashing in calculateFunctionHash (in aws-lambda/lib/function-hash.ts). This normalizes the order and eliminates non-determinism without changing execution semantics.

Alternatively, the VPC context provider could sort subnet IDs before returning them.

### Additional Information/Context

- The VERSION_LOCKED map correctly excludes properties that don't affect execution (ReservedConcurrentExecutions: false, Tags: false). However, while the set of subnets matters for Lambda execution, the order does not — Lambda doesn't behave differently based on subnet ordering.
- Feature flags enabled: @aws-cdk/aws-lambda:recognizeVersionProps, @aws-cdk/aws-lambda:recognizeLayerVersion
- Workaround: commit cdk.context.json to version control so VPC lookup results are cached between synth runs.

### AWS CDK Library version (aws-cdk-lib)

2.261.0

### AWS CDK CLI version

2.1129.0

### Node.js Version

22.x

### OS

Linux (Amazon Linux 2023, CI agent)

### Language

TypeScript

### Language Version

TypeScript 5.7

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start in aws-lambda/lib/function-hash.ts and inspect how VpcConfig.SubnetIds and SecurityGroupIds contribute to calculateFunctionHash. Reproduce the issue by deleting cdk.context.json and running cdk synth twice with the provided VPC lookup example. Done means unchanged subnet and security-group sets produce the same Lambda Version logical ID across synths.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.