Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code fbca2528ff fix: add retry and concurrency guard for Lambda delete in logic function driver
https://sonarly.com/issue/19252?type=bug

Concurrent workflow code step executions trigger parallel Lambda function delete-and-recreate cycles for the same function ID, exhausting AWS Lambda API rate limits and causing `TooManyRequestsException`.

Fix: Added error handling for concurrent Lambda operations in `LambdaDriver` (`lambda.driver.ts`):

1. **`delete()` method** — Added catch for three AWS exception types:
   - `ResourceNotFoundException`: Function was already deleted by a concurrent worker
   - `ResourceConflictException`: Function is in a pending state (e.g., being created/updated)
   - `TooManyRequestsException`: AWS Lambda API rate limit hit — safe to skip since the subsequent `CreateFunctionCommand` will either succeed (if function was deleted) or be caught by the new `ResourceConflictException` handler in `build()`

2. **`build()` CreateFunctionCommand** — Added `ResourceConflictException` catch so that when delete was skipped (due to rate limiting) or another worker already created the function, execution proceeds normally to `waitFunctionActive`.

3. **`ensureYarnInstallLambdaExists()` and `ensureBuilderLambdaExists()`** — Added the same `ResourceConflictException` handling for consistency, as these have the same TOCTOU race condition.

All three exception types are imported from `@aws-sdk/client-lambda`, alphabetically consistent with the existing `ResourceNotFoundException` import. The error-handling pattern matches `getLambdaExecutor()` which already uses the `if (!(error instanceof X)) throw error` idiom.
2026-03-28 17:47:41 +00:00
@@ -16,7 +16,9 @@ import {
type ListLayerVersionsCommandInput,
LogType,
PublishLayerVersionCommand,
ResourceConflictException,
ResourceNotFoundException,
TooManyRequestsException,
waitUntilFunctionActiveV2,
} from '@aws-sdk/client-lambda';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
@@ -392,7 +394,13 @@ export class LambdaDriver implements LogicFunctionDriver {
MemorySize: YARN_INSTALL_LAMBDA_MEMORY_MB,
};
await lambdaClient.send(new CreateFunctionCommand(params));
try {
await lambdaClient.send(new CreateFunctionCommand(params));
} catch (error) {
if (!(error instanceof ResourceConflictException)) {
throw error;
}
}
} finally {
await temporaryDirManager.clean();
}
@@ -481,7 +489,13 @@ export class LambdaDriver implements LogicFunctionDriver {
MemorySize: BUILDER_LAMBDA_MEMORY_MB,
};
await lambdaClient.send(new CreateFunctionCommand(params));
try {
await lambdaClient.send(new CreateFunctionCommand(params));
} catch (error) {
if (!(error instanceof ResourceConflictException)) {
throw error;
}
}
} finally {
await temporaryDirManager.clean();
}
@@ -799,7 +813,27 @@ export class LambdaDriver implements LogicFunctionDriver {
FunctionName: flatLogicFunction.id,
});
await (await this.getLambdaClient()).send(deleteFunctionCommand);
try {
await (await this.getLambdaClient()).send(deleteFunctionCommand);
} catch (error) {
if (
error instanceof ResourceNotFoundException ||
error instanceof ResourceConflictException
) {
// Already deleted by another concurrent worker, or function
// is in a pending state — safe to proceed.
return;
}
if (error instanceof TooManyRequestsException) {
// Rate limited — the function may or may not still exist.
// The subsequent CreateFunctionCommand will handle both cases
// (create succeeds, or ResourceConflictException if it exists).
return;
}
throw error;
}
}
}
@@ -938,7 +972,14 @@ export class LambdaDriver implements LogicFunctionDriver {
const command = new CreateFunctionCommand(params);
await (await this.getLambdaClient()).send(command);
try {
await (await this.getLambdaClient()).send(command);
} catch (error) {
if (!(error instanceof ResourceConflictException)) {
throw error;
}
// Function was concurrently created by another worker — safe to proceed.
}
} finally {
await temporaryDirManager.clean();
}