aws / aws/aws-cdk

LambdaRestApi does not trigger new deployment when Lambda is replaced via PhysicalName.GENERATE_IF_NEEDED

Open
#37,296 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### `LambdaRestApi` does not trigger new deployment when Lambda is replaced via `PhysicalName.GENERATE_IF_NEEDED`

### Describe the bug

When a `LambdaRestApi` is backed by a Lambda function using `PhysicalName.GENERATE_IF_NEEDED` (or any CDK-generated name), changes that cause CloudFormation to **replace** the Lambda function do not trigger a new API Gateway deployment. The API Gateway continues pointing at the old (now-deleted) Lambda ARN, returning `500 Internal Server Error` on every request.

This is a gap in the fix for #5306 (PR #8813). That fix added deployment-token tracking for the Lambda function name, but it's guarded by `Token.isUnresolved()`:

```javascript
// aws-apigateway/lib/integrations/lambda.js — LambdaIntegration.bind()
let deploymentToken;
Token.isUnresolved(functionName) || (deploymentToken = JSON.stringify({ functionName }));
```

When `functionName` is a Token (which it is for `GENERATE_IF_NEEDED`, cross-stack references, or the default unnamed case), `deploymentToken` remains `undefined`. Downstream in `Method`, `addToLogicalId` receives `integrationToken: undefined`, so the deployment's logical ID never changes regardless of what happens to the Lambda.

### Expected Behavior

Any change to the backing Lambda function — including replacement triggered by a physical name change — should produce a new API Gateway deployment so the stage points at the new function.

### Current Behavior

1. Deploy a `LambdaRestApi` with a Lambda using `PhysicalName.GENERATE_IF_NEEDED` — works fine
2. Make an unrelated change that alters the Lambda's generated physical name (e.g., changing environment resolution in a cross-stack reference, or modifying the construct tree)
3. CloudFormation replaces the Lambda (delete old + create new with different name)
4. CloudFormation updates the API Gateway Method integration URI to point at the new Lambda ARN
5. **No new Deployment is created** — the stage still references the old deployment
6. All requests return `500 Internal Server Error` because the old deployment's cached integration points at the deleted Lambda

There is no warning or error during `cdk synth` or `cdk deploy`. The Lambda and API Gateway both appear healthy in the console. The only symptom is silent 500s.

### Reproduction Steps

```typescript
import { Stack, PhysicalName, Duration } from "aws-cdk-lib";
import { LambdaRestApi } from "aws-cdk-lib/aws-apigateway";
import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda";

// Stack A: Lambda with generated name
const fn = new Function(this, "MyFunction", {
functionName: PhysicalName.GENERATE_IF_NEEDED,
runtime: Runtime.NODEJS_20_X,
handler: "index.handler",
code: Code.fromInline('exports.handler = async () => ({ statusCode: 200, body: "ok" })'),
});

// Stack A: API Gateway
const api = new LambdaRestApi(this, "MyApi", { handler: fn });

// Stack B (references fn from Stack A):
// Any change that alters how Stack B resolves the cross-stack reference
// to fn will change the generated physical name, triggering replacement.
// After deployment: API Gateway returns 500.
```

**Real-world trigger:** In our case, changing `super(scope, id)` to `super(scope, id, props)` in a downstream stack that references the Lambda altered CDK's environment-aware name generation, producing a different physical name and triggering replacement.

### Workaround

Add `addToLogicalId` manually after creating the `LambdaRestApi`:

```typescript
const api = new LambdaRestApi(this, "MyApi", { handler: fn });

if (api.latestDeployment) {
api.latestDeployment.addToLogicalId({
lambdaFunctionName: fn.functionName,
});
}
```

This works because `addToLogicalId` accepts Tokens — it serializes them into the logical ID hash via CloudFormation intrinsics, so when the resolved function name changes, the deployment hash changes too.

### Suggested Fix

In `LambdaIntegration.bind()`, remove the `Token.isUnresolved` guard and always include the function name in the deployment token. Alternatively, use the Lambda function's **logical ID** (which is always a concrete string) instead of the physical name:

```javascript
// Option A: Always include functionName (works with Tokens via CFN intrinsics)
const deploymentToken = JSON.stringify({ functionName });

// Option B: Use the logical resource ID (always concrete)
const deploymentToken = JSON.stringify({
functionLogicalId: this.handler.node.defaultChild?.logicalId
});
```

### Environment

- **CDK CLI Version:** 2.1007.0
- **Framework Version:** 2.1007.0
- **Node.js Version:** v24.13.0
- **OS:** Amazon Linux 2 (CI/CD) / macOS (local)
- **Language:** TypeScript

### Other Information

- The original #5306 fix (PR #8813) was specifically for explicit `functionName` changes, which is why it used `Token.isUnresolved` — but the same problem affects any Lambda replacement, not just explicit name changes.
- `PhysicalName.GENERATE_IF_NEEDED` is the recommended pattern for cross-stack Lambda references per CDK docs, making this a very common configuration.
- The failure is silent and difficult to diagnose: Lambda logs show zero invocations, API Gateway logs show integration errors, and CloudFormation reports a successful deployment.

Contributor guide

Open the contributing guide

Research direction

Start in aws-apigateway/lib/integrations/lambda.js at LambdaIntegration.bind(), then follow how Method passes integrationToken to addToLogicalId. Verify how unresolved functionName values affect the deployment logical ID and confirm that a generated-name Lambda replacement causes a new API Gateway deployment. Done means the deployment tracks the backing Lambda change without requiring the documented manual workaround.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.