A version for this Lambda function exists just from upgrading the CDK version
This page has been translated by machine translation. View original
Introduction
Hello, I'm Junkichi.
After upgrading aws-cdk-lib and running cdk deploy, I encountered the following error.
CREATE_FAILED AWS::Lambda::Version
A version for this Lambda function exists ( 1 ). Modify the function to create a new version.
(HandlerErrorCode: AlreadyExists)
I hadn't changed the Lambda function code or any stack configuration — the only change was the aws-cdk-lib version. That alone caused the AWS::Lambda::Version creation to fail and the stack to roll back.
Conclusion
The issue where "the synthesized template is identical yet only the AWS::Lambda::Version hash (logical ID) changes" has been a known problem in CDK for some time (see Issue #26739, etc.). This error was another instance of that.
Specifically, when upgrading aws-cdk-lib from 2.258.1 to 2.259.0, the mutable alias Runtime.NODEJS_LATEST changed its target from nodejs22.x to nodejs24.x (PR #38031), and this leaked into the Version hash via the Lambda Insights layer's (an ARN-referenced imported layer) compatibleRuntimes. This happens even if the function's own runtime is pinned to NODEJS_24_X.
The fix is to embed the aws-cdk-lib version in the function's description.
description: `built-with-aws-cdk-lib@${require('aws-cdk-lib/package.json').version}`,
What is the error "A version for this Lambda function exists"?
To understand this error, you need to look at both the Lambda versioning specification and how CloudFormation/CDK handles resources.
Lambda versioning specification
Lambda assigns a monotonically increasing number (1, 2, 3, …) each time a version is published, and numbers are never reused even after deletion. A new version is only published when $LATEST has changed since the last published version. If neither the code nor the configuration has changed, calling PublishVersion will not produce a new version. In other words, this error does not occur because "the same number was specified," but rather because "an attempt was made to publish a new version when the function had not changed." The error message Modify the function to create a new version reflects exactly that.
Handling Lambda versions with CloudFormation/CDK
In CloudFormation, a Lambda version is a standalone resource type called AWS::Lambda::Version, and resources are identified by their logical ID. If the logical ID changes, it is treated as a "different resource," triggering a creation of a new resource (and deletion of the old one). CDK constructs the logical ID for a Version using a hash computed from the function's code and version-locked properties (such as Runtime, Description, Environment, etc. — properties that require publishing a new version when changed). From CDK's perspective, the flow is: "function configuration changed → hash changed → logical ID changed → create a new Version resource."
Why the error occurs
CDK determines that "the function has changed" and tries to create an AWS::Lambda::Version with a new logical ID, but Lambda's PublishVersion determines that "the function has not changed" and refuses to publish a new version. This mismatch in judgment produces A version for this Lambda function exists (HandlerErrorCode: AlreadyExists). Therefore, to find the root cause, you need to trace "why CDK judged that a change occurred" — that is, "what caused the hash input for the logical ID to change."
There are prior examples of bugs caused by CDK's hash calculation. (#26739 / #14428). The CDK official README also states:
a bug was introduced in this calculation that caused the logical id to change when it was not required (...) This caused the deployment to fail since the Lambda service does not allow creating duplicate versions.
References: Lambda function versioning / AWS CDK aws_lambda README (Versions)
The configuration where the issue occurred
It can be reproduced with a minimal setup: a NodejsFunction (arm64 / nodejs24.x pinned) with a Lambda Insights layer attached, and currentVersion referenced from an Alias.
const fn = new NodejsFunction(this, 'Fn', {
runtime: Runtime.NODEJS_24_X, // The function's own runtime is pinned
architecture: Architecture.ARM_64,
entry: path.join(__dirname, '..', 'lambda', 'handler.ts'),
handler: 'handler',
// This is the sole trigger. Removing it prevents the logical ID from changing on 2.258.1 -> 2.259.0.
insightsVersion: LambdaInsightsVersion.VERSION_1_0_498_0,
bundling: {
forceDockerBundling: false,
},
});
new Alias(this, 'LiveAlias', {
aliasName: 'live',
version: fn.currentVersion,
});
Verification
Verification was performed in the following environment.
aws-cdkCLI2.1132.0(kept fixed during comparison; onlyaws-cdk-libwas swapped)- Function runtime:
NODEJS_24_Xpinned - Build:
NodejsFunction+ esbuild only (esbuild is used even without specifyingforceDockerBundling, but made explicit for clarity)
Comparing logical IDs with local synth
I ran cdk synth with combinations of Insights presence × version and compared the hash suffix of the AWS::Lambda::Version logical ID.
| aws-cdk-lib | with insights | without insights |
|---|---|---|
| 2.258.1 | …be4938273272… |
…7960d9a4b981… |
| 2.259.0 | …0e85dd15c09c… (changed) |
…7960d9a4b981… (unchanged) |
The logical ID changes between 2.258.1 and 2.259.0 only when Insights is attached; removing it keeps the logical ID identical across both versions. This controlled experiment confirms that the Lambda Insights layer is the sole trigger.
At this point, aside from the AWS::Lambda::Version logical ID and CDKMetadata, the synthesized templates are byte-for-byte identical between versions. The function resource itself has not changed at all.
Reproducing the failure with an actual deployment
Having confirmed that the logical ID changes locally, I deployed to a verification AWS account to observe the behavior.
- Deploy with 2.258.1 → Success.
AWS::Lambda::Versionversion 1 is published, and aliaslivepoints to 1. - Without changing any code, upgrade to 2.259.0 and run
cdk diff: - Deploy with 2.259.0 → Failure
CREATE_FAILED AWS::Lambda::Version
A version for this Lambda function exists ( 1 ). Modify the function to create a new version.
(HandlerErrorCode: AlreadyExists)
CDK attempts to create a Version with a new logical ID, but since the function's actual content is identical to version 1, Lambda refuses to publish a new version.
Root cause
By capturing and comparing the inputs passed to calculateFunctionHash during the synth process, the only differing input between versions was the NODEJS_LATEST entry in the Insights layer's compatibleRuntimes (nodejs22.x ↔ nodejs24.x). Here is the source trace.
First, NODEJS_LATEST is defined as a mutable alias that "changes over time" (aws-lambda/lib/runtime.ts).
// isVariable: true = a mutable alias whose target changes over time.
// In v2.259.0 it is 'nodejs24.x' (it was 'nodejs22.x' in 2.258.1; changed in PR #38031).
public static readonly NODEJS_LATEST =
new Runtime('nodejs24.x', RuntimeFamily.NODEJS, { supportsInlineCode: true, isVariable: true });
// An array collecting all Runtimes. Each Runtime is pushed here upon instantiation (constructor).
public static readonly ALL = new Array<Runtime>();
Runtime.ALL is an array containing all Runtimes, including the mutable alias NODEJS_LATEST. When a layer is imported from an ARN, its compatibleRuntimes is populated with the entire Runtime.ALL (aws-lambda/lib/layers.ts).
public static fromLayerVersionArn(scope: Construct, id: string, layerVersionArn: string): ILayerVersion {
return LayerVersion.fromLayerVersionAttributes(scope, id, {
layerVersionArn,
compatibleRuntimes: Runtime.ALL, // ← imported layer gets Runtime.ALL
});
}
Since insightsVersion is added via this fromLayerVersionArn, the Insights layer's compatibleRuntimes becomes Runtime.ALL.
CDK then, inside calculateFunctionHash (aws-lambda/lib/function-hash.ts), which computes the Version hash, adds the result of calculateLayersHash to the hash input when the feature flag recognizeLayerVersion is enabled (the default in v2).
// L11-27: In addition to function properties, the layers hash is conditionally mixed in.
export function calculateFunctionHash(fn: LambdaFunction, additional: string = '') {
// ...processing that assembles function properties into stringifiedConfig is omitted...
if (FeatureFlags.of(fn).isEnabled(LAMBDA_RECOGNIZE_LAYER_VERSION)) {
stringifiedConfig = stringifiedConfig + calculateLayersHash([...fn._layers].sort());
}
return md5hash(stringifiedConfig + additional);
}
Inside calculateLayersHash (relevant location), the branch for imported layers (layerResource === undefined) directly uses compatibleRuntimes as hash input.
function calculateLayersHash(layers: ILayerVersion[]): string {
const layerConfig: {[key: string]: any } = {};
for (const layer of layers) {
const layerResource = layer.node.defaultChild as CfnResource;
// if there is no layer resource, then the layer was imported
// and we will include the layer arn and runtimes in the hash
if (layerResource === undefined) {
if (!Token.isUnresolved(layer.layerVersionArn)) {
layerConfig[layer.layerVersionArn] = layer.compatibleRuntimes; // ← here
} else {
layerConfig[layer.node.id] = {
arn: stack.resolve(layer.layerVersionArn),
runtimes: layer.compatibleRuntimes?.map(r => r.name),
};
}
continue;
}
// Owned layers (with CfnLayerVersion) take this path
// and do not directly reference compatibleRuntimes.
const { properties } = resolveSingleResourceProperties(stack, layerResource);
layerConfig[layer.node.id] = sortLayerVersionProperties(properties);
}
return md5hash(JSON.stringify(layerConfig));
}
The logical ID changes for the following reasons.
- The name of
NODEJS_LATESTchanges fromnodejs22.x→nodejs24.x(2.259.0 / PR #38031). Runtime.ALLincludes thatNODEJS_LATEST.- The imported layer's
compatibleRuntimesisRuntime.ALL. - With
recognizeLayerVersion(enabled by default in v2),calculateLayersHashuseslayer.compatibleRuntimesdirectly as hash input. - The name of
NODEJS_LATESTinsideRuntime.ALLshifts from 22→24 → the layer hash changes → the Version hash changes. - CDK attempts to create an
AWS::Lambda::Versionwith a new logical ID, but the function's actual content is the same → deployment fails withAlreadyExists.
This is where the mismatch lies between Lambda's API and CDK's hash calculation logic. What Lambda's version publishing looks at for layers is only the ARNs of the layer versions attached to the function. Since a layer version is immutable after publication and the same ARN always points to the same code, if the ARN is the same between 2.258.1 and 2.259.0 as in this case, from the service's perspective it should be "the same function with the same layer attached." However, CDK includes in the logical ID hash even the compatibleRuntimes (Runtime.ALL) held by the imported layer.
Confirming it is not specific to Lambda Insights
If the root cause is fromLayerVersionArn's compatibleRuntimes: Runtime.ALL, the same issue should occur with any custom layer referenced by ARN, not just Lambda Insights. I ran cdk synth with a custom layer using a dummy ARN in different ways on both 2.258.1 and 2.259.0, and compared the Version logical IDs.
| How the layer is provided | compatibleRuntimes |
2.258.1 vs 2.259.0 |
|---|---|---|
LayerVersion.fromLayerVersionArn(arn) |
Runtime.ALL (includes NODEJS_LATEST; auto-injected) |
Logical ID changes (…57c4b3a6… → …fdc5af42…) |
LayerVersion.fromLayerVersionAttributes({ arn, compatibleRuntimes: [NODEJS_24_X] }) |
Explicitly specified (no mutable aliases) | Does not change |
new LayerVersion(...) (owned) |
Resource properties path | Does not change |
The same logical ID change occurred with a custom layer referenced by ARN via fromLayerVersionArn, confirming it is not specific to Lambda Insights. On the other hand, even with the same ARN reference, if compatibleRuntimes is explicitly specified via fromLayerVersionAttributes without including mutable aliases, the logical ID does not change. Owned layers do not change either.
Therefore, this affects all layers imported via fromLayerVersionArn.
Fix
Embed the aws-cdk-lib version in the function's description.
description: `built-with-aws-cdk-lib@${require('aws-cdk-lib/package.json').version}`,
Description is a version-locked property — that is, a property included in the hash — and also a property that "allows a new version to be published when changed." Upgrading CDK changes the Version's logical ID, but since the CDK version embedded in the description also necessarily changes at that point, the function's actual content (configuration) really does change. This allows Lambda to publish a new version, preventing the A version for this Lambda function exists failure. Since the version is retrieved automatically via require, no manual version updates are needed.
This was also verified on a real deployment.
| Deployment | Function Description | Result |
|---|---|---|
| 2.258.1 (no description) → 2.259.0 | none | Failure (AlreadyExists) |
| 2.258.1 (with description) | built-with-aws-cdk-lib@2.258.1 |
Success (version 2) |
| 2.259.0 (with description) | built-with-aws-cdk-lib@2.259.0 |
Success (version 3, alias → 3) |
Setting the feature flag @aws-cdk/aws-lambda:recognizeLayerVersion to false also works as a fix, but it means actual layer version changes are no longer reflected in the Version hash, making it a side effect in cases where you want to detect layer updates.
The description approach has the side effect that every CDK upgrade publishes one new version even when the hash would not have actually changed, but I think it is relatively easy to adopt.
Summary
- The root cause of this error is that
NODEJS_LATESTchanged from nodejs22.x→24.x in 2.259.0, and this leaked into the Version hash via the imported layer'scompatibleRuntimes(Runtime.ALL). - This is not specific to Lambda Insights; it reproduces with any layer referenced by ARN via
fromLayerVersionArn(such as ADOT / Parameters and Secrets Extension, etc.). - The fix is to set the function's
descriptiontobuilt-with-aws-cdk-lib@${require('aws-cdk-lib/package.json').version}. Since upgrading CDK always changes the description, Lambda can reliably publish a new version.
I hope this is helpful to someone.
