A version for this Lambda function exists
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 the stack configuration — the only thing I changed was the version of aws-cdk-lib. That alone caused the creation of AWS::Lambda::Version to fail and the stack to roll back.
Conclusion
The issue where "the synthesized template is identical yet only the hash (logical ID) of AWS::Lambda::Version 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 destination of the mutable alias Runtime.NODEJS_LATEST changed from nodejs22.x to nodejs24.x (PR #38031). This change leaked into the Version hash via the compatibleRuntimes of the Lambda Insights layer (an imported layer referenced by ARN). This happens even if you have pinned the function's own runtime 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 Version Specification
Lambda assigns a monotonically increasing number (1, 2, 3, …) each time a version is published, and never reuses a number even if a version is deleted. A new version is only published when $LATEST has changed from the previously 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 "a new version was requested when there were no changes to the function." The error message Modify the function to create a new version illustrates this.
Handling Lambda Versions with CloudFormation/CDK
In CloudFormation, Lambda versions are 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 creation of a new one (and deletion of the old one). CDK constructs the logical ID for this Version using a hash computed from the function's code and version-locked properties (such as Runtime, Description, Environment, etc. — items 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 attempts to create a new 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 identify the root cause, you need to trace "why CDK determined there was a change" — i.e., "what caused the hash input for the logical ID to shift."
Bugs caused by CDK's hash calculation have precedent. (#26739 / #14428). The official CDK README also states the following:
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)
Configuration Where the Issue Occurred
This can be reproduced with a minimal configuration. It's a stack with just 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 explicitly stated for clarity)
Comparing Logical IDs with Local Synth
I ran cdk synth for combinations of Insights presence/absence × version, then compared the hash at the end 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 from 2.258.1 → 2.259.0 only when Insights is attached; removing it produces identical results across both versions. This controlled experiment confirms that the Lambda Insights layer is the sole trigger.
At this point, excluding the AWS::Lambda::Version logical ID and CDKMetadata, the synthesized templates are byte-for-byte identical across versions. The function resource itself has not changed at all.
Reproducing the Failure with an Actual Deployment
Having confirmed locally that the logical ID changes, I performed an actual deployment 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 itself 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 input that differed between versions was the NODEJS_LATEST entry in the Insights layer's compatibleRuntimes (nodejs22.x ↔ nodejs24.x). I traced through the source.
First, NODEJS_LATEST is defined as a mutable alias whose target "changes over time" (aws-lambda/lib/runtime.ts).
// isVariable: true = a mutable alias whose target changes over time.
// In v2.259.0, it's '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 (in the constructor).
public static readonly ALL = new Array<Runtime>();
Runtime.ALL is an array collecting 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.
Then, in calculateFunctionHash (aws-lambda/lib/function-hash.ts), which CDK uses to compute the Version hash, if the feature flag recognizeLayerVersion is enabled (the default in v2), the result of calculateLayersHash is added to the hash input.
// L11-27: In addition to function properties, layer hashes are conditionally mixed in.
export function calculateFunctionHash(fn: LambdaFunction, additional: string = '') {
// ...processing that collects 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);
}
In calculateLayersHash (relevant section), the code directly uses compatibleRuntimes from the imported layer (the branch where layerResource === undefined) 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;
}
// For owned layers (those with a CfnLayerVersion), this path is used,
// and compatibleRuntimes is not consulted directly.
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
recognizeLayerVersionenabled (the default in v2),calculateLayersHashdirectly useslayer.compatibleRuntimesas hash input. - The name of
NODEJS_LATESTinsideRuntime.ALLshifts 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 itself is unchanged → deployment fails withAlreadyExists.
Here lies the mismatch between the Lambda API and CDK's hash calculation logic. When Lambda publishes a version, the only layer-related information it looks at is the ARN of the layer version 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 identical 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 the imported layer's compatibleRuntimes (Runtime.ALL) in the logical ID hash input.
Confirming This Is Not Specific to Lambda Insights
If the root cause lies in fromLayerVersionArn's compatibleRuntimes: Runtime.ALL, then the same thing should occur with a custom layer referenced by ARN, not just Lambda Insights. I ran cdk synth with 2.258.1 and 2.259.0 for a custom layer using a fictitious ARN, varying only how the layer was provided, 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] }) |
Explicit (no mutable aliases) | Does not change |
new LayerVersion(...) (owned) |
Resource properties path | Does not change |
This is not specific to Lambda Insights — the same logical ID change occurs with custom layers referenced by ARN via fromLayerVersionArn. On the other hand, even with an ARN reference, explicitly specifying compatibleRuntimes without mutable aliases via fromLayerVersionAttributes prevents the change. Owned layers also remain unchanged.
Therefore, this affects all layers referenced by ARN 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 — meaning it is included in the hash and is also something that "can be changed to publish a new version." When you upgrade CDK, the Version logical ID changes, but the CDK version embedded in the description also necessarily changes, so the function's actual (configuration) changes. Lambda can then publish a new version, and the A version for this Lambda function exists failure does not occur. Since the version is retrieved automatically via require, no manual version updates are needed.
I also confirmed this with an actual 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 causes actual layer version changes to no longer be reflected in the Version hash, which becomes a side effect in cases where you want to detect layer updates.
The description approach has the side effect of always publishing one new version each time CDK is upgraded, even if the hash hasn't actually changed, but I think it's relatively easy to adopt.
Summary
- The cause of this error was 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 add
built-with-aws-cdk-lib@${require('aws-cdk-lib/package.json').version}to the function'sdescription. Since upgrading CDK always changes the description, Lambda can reliably publish a new version.
I hope this is helpful to someone.