A version for this Lambda function exists

A version for this Lambda function exists

I updated aws-cdk-lib and changed nothing else in my code, yet the Lambda deployment failed with "A version for this Lambda function exists." I will isolate the phenomenon where only the Version hash changes despite the synthesized template being identical, then document the root cause and remediation.
2026.07.23

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-cdk CLI 2.1132.0 (kept fixed during comparison; only aws-cdk-lib was swapped)
  • Function runtime: NODEJS_24_X pinned
  • Build: NodejsFunction + esbuild only (esbuild is used even without specifying forceDockerBundling, 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.

  1. Deploy with 2.258.1 → Success. AWS::Lambda::Version version 1 is published, and alias live points to 1.
  2. Without changing any code, upgrade to 2.259.0 and run cdk diff:
  3. 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.xnodejs24.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.

  1. The name of NODEJS_LATEST changes from nodejs22.xnodejs24.x (2.259.0 / PR #38031).
  2. Runtime.ALL includes that NODEJS_LATEST.
  3. The imported layer's compatibleRuntimes is Runtime.ALL.
  4. With recognizeLayerVersion enabled (the default in v2), calculateLayersHash directly uses layer.compatibleRuntimes as hash input.
  5. The name of NODEJS_LATEST inside Runtime.ALL shifts 22→24 → the layer hash changes → the Version hash changes.
  6. CDK attempts to create an AWS::Lambda::Version with a new logical ID, but the function itself is unchanged → deployment fails with AlreadyExists.

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_LATEST changed from nodejs22.x→24.x in 2.259.0, and this leaked into the Version hash via the imported layer's compatibleRuntimes (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's description. Since upgrading CDK always changes the description, Lambda can reliably publish a new version.

I hope this is helpful to someone.

Share this article