aws-lambda: currentVersion logical ID non-deterministic due to unsorted VPC SubnetIds and non-deterministic calculateLayersHash for imported layers
- 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
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