LambdaRestApi does not trigger new deployment when Lambda is replaced via PhysicalName.GENERATE_IF_NEEDED
- 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
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