AWS Lambda skill for Claude Code
Fast cold starts, safe retries, no silent drops. Claude writes Lambda handlers that create clients once at init, stay well inside timeouts, and return partial batch failures so one bad SQS message does not replay the rest. It measures Init Duration before tuning, chooses between bundling, arm64, SnapStart and provisioned concurrency, and makes sure every async trigger has a failure destination and alarms.
- name
aws-lambda - size184 lines · ≈2,250 tokens
- files2
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Builds and tunes AWS Lambda functions - handler structure with init outside the handler, cold start reduction (bundling, arm64, SnapStart, provisioned concurrency), memory and timeout sizing, packaging with esbuild, layers or container images, configuration and secrets, event source mappings for SQS, Kinesis, DynamoDB Streams and API Gateway, partial batch failures, idempotency and Powertools for logging, tracing and metrics. Use when writing or debugging a Lambda function, fixing cold starts, timeouts or throttling, or wiring a queue or API to Lambda. Try asking
- Our Node Lambda behind API Gateway has 3-second cold starts. Fix it
- Wire this SQS queue to a Lambda with retries and a dead-letter queue
- Why does our Lambda process the same S3 upload twice?
Use it when
- Writing or reviewing Lambda handler code
- Cold starts, timeouts or throttles are hurting latency
- Connecting SQS, streams, S3 or API Gateway to a function
- Adding structured logging, tracing and idempotency
Skip it when
How to check the result
Invoking the alias succeeds with p99 duration well under the timeout, a deliberately failing event reaches the DLQ or destination, and alarms exist for errors, throttles and queue depth.
The common mistake it prevents
Catching every exception and returning success, which defeats Lambda retries and leaves the dead-letter queue empty while data is lost.
A production Lambda initializes clients once, finishes well inside its timeout, emits structured logs with the request ID, tolerates duplicate and partial-batch delivery, has a dead-letter or failure destination for every async source, and is sized from measured duration and memory rather than defaults.
Workflow
- [ ] 1. Identify runtime, architecture, memory, timeout, trigger(s), IaC tool (SAM, CDK, Terraform, Serverless)
- [ ] 2. Read the handler: what runs at init vs per invocation
- [ ] 3. Check the event source contract (batching, retries, failure handling)
- [ ] 4. Fix the code: init outside handler, idempotency, partial batch responses
- [ ] 5. Package small; configure env/secrets; set memory/timeout from measurements
- [ ] 6. Add Powertools logging/tracing/metrics and alarms
- [ ] 7. Test locally and invoke in a dev account; check logs and metrics
Handler shape
// Node.js 22 (ESM). Init code runs once per execution environment.
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { Logger } from "@aws-lambda-powertools/logger";
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2, Context } from "aws-lambda";
const logger = new Logger({ serviceName: "orders" });
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.TABLE_NAME!;
export const handler = async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
logger.addContext(context);
const id = event.pathParameters?.id;
if (!id) return { statusCode: 400, body: JSON.stringify({ error: "id required" }) };
const { Item } = await ddb.send(new GetCommand({ TableName: TABLE, Key: { pk: `ORDER#${id}` } }));
return Item
? { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify(Item) }
: { statusCode: 404, body: JSON.stringify({ error: "not found" }) };
};
# Python 3.13
import json, os
import boto3
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.typing import LambdaContext
logger = Logger(service="orders")
tracer = Tracer()
table = boto3.resource("dynamodb").Table(os.environ["TABLE_NAME"]) # created once
@logger.inject_lambda_context
@tracer.capture_lambda_handler
def handler(event: dict, context: LambdaContext) -> dict:
item = table.get_item(Key={"pk": f"ORDER#{event['pathParameters']['id']}"}).get("Item")
return {"statusCode": 200 if item else 404, "body": json.dumps(item or {"error": "not found"}, default=str)}
Never leave work running after the handler returns: the environment freezes, and timers, unawaited promises or background threads resume (or vanish) in a later invocation. Global state persists between invocations in the same environment: good for clients and caches, wrong for per-request data.
Limits that shape designs
| Limit | Value |
|---|---|
| Timeout | max 900 s (15 min) |
| Memory | 128 MB-10,240 MB; CPU scales with memory (about 1 vCPU at 1,769 MB) |
| Sync payload (request and response) | 6 MB |
| Zip package | 50 MB zipped direct upload, 250 MB unzipped including layers |
| Container image | 10 GB |
/tmp | 512 MB default, up to 10,240 MB |
| Env vars | 4 KB total |
| API Gateway REST/HTTP integration timeout | 29 s default (REST regional can be raised by quota request); HTTP APIs 30 s |
Work longer than these belongs in Step Functions, ECS/Fargate tasks, or a queue-driven chain.
Cold starts
Measure first: CloudWatch REPORT lines include Init Duration for cold starts.
aws logs filter-log-events --log-group-name /aws/lambda/orders-api \
--filter-pattern '"Init Duration"' --max-items 20 --query 'events[].message'
| Lever | Effect |
|---|---|
| Bundle and tree-shake (esbuild), import only needed SDK v3 clients | Fewer files to load; often the largest win for Node |
| Lazy-import rarely used heavy modules | Shorter init for common paths |
| More memory | More CPU during init and execution; often cheaper overall when duration drops |
| arm64 (Graviton) | About 20% cheaper per GB-second; similar or better performance for most code |
| SnapStart (Java 11+, Python 3.12+, .NET 8+) | Restores from a snapshot; requires published versions and care with uniqueness (re-seed randomness, refresh credentials/connections in restore hooks) |
| Provisioned concurrency on an alias | Pre-initialized environments; pay for idle; use with Application Auto Scaling schedules |
| Avoid VPC unless needed | VPC cold start penalty is mostly gone (Hyperplane ENIs), but NAT and DNS add latency and cost |
Use AWS Lambda Power Tuning (open-source Step Functions state machine) to find the memory size with the best cost/duration trade-off.
Packaging and configuration
- Node: bundle with esbuild (
--bundle --platform=node --target=node22 --format=esm, plus acreateRequirebanner if CJS deps needrequire). The runtime includes AWS SDK v3, but bundling your own pins the version you tested. - Python: build dependencies for the target architecture
(
pip install --platform manylinux2014_aarch64 --only-binary=:all: -t build/ -r requirements.txt) or use container images / SAMsam build --use-container. - Config via environment variables; secrets via SSM Parameter Store or Secrets Manager fetched at init and cached (Powertools Parameters utility, or the Parameters and Secrets Lambda Extension). Never put secrets in plain env vars visible in the console if policy forbids it.
- Publish versions and point an alias (
live) at them; deploy with CodeDeploy traffic shifting (SAMDeploymentPreference) for gradual rollout with alarms.
Event sources and failure handling
| Source | Invocation | Retry behavior | Must configure |
|---|---|---|---|
| API Gateway / Function URL / ALB | Sync | Caller decides | Timeouts, error mapping |
| S3, SNS, EventBridge | Async | 2 retries by default, then dropped | On-failure destination or DLQ, MaximumEventAgeInSeconds, MaximumRetryAttempts |
| SQS | Poll (event source mapping) | Message returns after visibility timeout until maxReceiveCount, then DLQ | Visibility timeout >= 6x function timeout, ReportBatchItemFailures, redrive policy, MaximumConcurrency |
| Kinesis / DynamoDB Streams | Poll, ordered per shard | Retries the batch until success or expiry, blocking the shard | BisectBatchOnFunctionError, MaximumRetryAttempts, on-failure destination, ReportBatchItemFailures |
Partial batch failure for SQS (Powertools batch utility):
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
processor = BatchProcessor(event_type=EventType.SQS)
def record_handler(record):
process_order(json.loads(record.body)) # raise to mark only this record failed
def handler(event, context):
return process_partial_response(event=event, record_handler=record_handler,
processor=processor, context=context)
All sources deliver at least once: make handlers idempotent (Powertools Idempotency utility with a DynamoDB table, keyed on a stable event field). For detailed per-source settings and sample events, read references/event-sources.md.
Concurrency
- Account concurrency default is 1,000 per region (can be lower on new accounts; request increases). One hot function can starve others: set reserved concurrency on critical and on noisy functions.
- Reserved concurrency also caps downstream load (e.g. database connections). For relational databases use RDS Proxy or connection-light drivers.
- Throttles (
Throttlesmetric) on async sources are retried automatically; on sync sources they surface as 429/502 to callers.
Gotchas
- Recursive loops (Lambda writes to the S3 bucket or queue that triggers it) burn money fast; Lambda’s recursive loop detection stops some (SQS, SNS, S3) but design them out.
- A handler that catches all exceptions and returns normally hides failures from retries and DLQs.
context.getRemainingTimeInMillis()lets long loops stop and checkpoint before timeout.- Timeouts kill the environment; the next invocation cold-starts.
- Logging full events can leak PII and cost a lot in CloudWatch Logs; set log retention (never “Never expire”) and log selectively.
Verification
sam local invoke OrdersFunction -e events/get-order.json # local, if SAM
aws lambda invoke --function-name orders-api:live \
--cli-binary-format raw-in-base64-out --payload file://events/get-order.json out.json && cat out.json
aws logs tail /aws/lambda/orders-api --follow --since 10m
Confirm: no errors in logs, duration well under the timeout at p99, Init Duration
improved if cold starts were the target, DLQ/destination receives a deliberately failing
event, and alarms exist on Errors, Throttles, DLQ depth, and (for streams) IteratorAge.
Bundled files
Loaded only when a step in the skill needs them. Review them here before installing.
references/event-sources.md
# Event source details
Read the section for the trigger you are wiring.
## SQS
- Batch size up to 10,000 for standard queues (with `MaximumBatchingWindowInSeconds`),
up to 10 for FIFO. Start with 10 and a 0-5 s window.
- Visibility timeout of the queue: at least 6x the function timeout (AWS guidance), so
messages are not redelivered while a retried invocation is still running.
- Turn on `FunctionResponseTypes: [ReportBatchItemFailures]` and return
`{"batchItemFailures": [{"itemIdentifier": "<messageId>"}]}`. Without it, one bad
message makes the whole batch retry, duplicating the successful ones.
- Redrive policy on the source queue: `maxReceiveCount` 3-5 to a DLQ. Alarm on the DLQ's
`ApproximateNumberOfMessagesVisible > 0`.
- `ScalingConfig.MaximumConcurrency` (2-1000) caps concurrent invocations per event
source mapping without reserved concurrency side effects (throttling-induced retries).
- FIFO: ordering per `MessageGroupId`; a failing message blocks its group until it
succeeds or goes to the DLQ.
SAM:
```yaml
OrdersWorker:
Type: AWS::Serverless::Function
Properties:
Timeout: 30
Events:
Queue:
Type: SQS
Properties:
Queue: !GetAtt OrdersQueue.Arn
BatchSize: 10
MaximumBatchingWindowInSeconds: 2
FunctionResponseTypes: [ReportBatchItemFailures]
ScalingConfig: { MaximumConcurrency: 20 }
OrdersQueue:
Type: AWS::SQS::Queue
Properties:
VisibilityTimeout: 180
RedrivePolicy: { deadLetterTargetArn: !GetAtt OrdersDLQ.Arn, maxReceiveCount: 5 }
```
## Kinesis and DynamoDB Streams
- Records are ordered per shard; a failing batch blocks the shard until success or
record expiry (24 h default for Kinesis, 24 h for DynamoDB Streams).
- Always set: `MaximumRetryAttempts` (e.g. 3), `MaximumRecordAgeInSeconds`,
`BisectBatchOnFunctionError: true`, `DestinationConfig.OnFailure` (SQS or SNS) to
capture metadata of dropped batches, and `ReportBatchItemFailures` (return the
sequence number of the first failed record).
- `ParallelizationFactor` (1-10) processes multiple batches per shard concurrently while
keeping per-partition-key order.
- Alarm on `IteratorAge`: growing age means the consumer is falling behind or stuck.
- Event filtering (`FilterCriteria`) drops irrelevant records before invocation (cheaper).
## API Gateway and Function URLs
- HTTP API (v2 payload) is cheaper and simpler than REST API; REST API adds usage plans,
API keys, request validation, WAF integration on more configurations, and private APIs.
- Payload format 2.0: `event.requestContext.http.method`, `event.rawPath`, cookies in
`event.cookies`; return `{statusCode, headers, body, cookies}` or a plain object (HTTP API
serializes it as JSON with 200).
- Binary responses: set `isBase64Encoded: true` and base64 the body.
- Function URLs: simplest public HTTPS endpoint; auth `AWS_IAM` or `NONE`; supports
response streaming (Node.js managed runtime) for long or incremental responses.
- Integration timeout is the hard ceiling for synchronous work; push longer tasks to a queue
and return 202.
## S3 events
- Delivered asynchronously, at least once, possibly out of order and possibly delayed.
- Object keys in events are URL-encoded (spaces become `+`): decode with
`decodeURIComponent(key.replace(/\+/g, " "))` or `urllib.parse.unquote_plus(key)`.
- Use EventBridge notifications for S3 when you need filtering by more than prefix/suffix
or multiple consumers.
- Never write output to the same bucket/prefix that triggers the function without a
prefix filter that excludes outputs.
## EventBridge and SNS
- Async invocation: configure `MaximumRetryAttempts` (0-2), `MaximumEventAgeInSeconds`,
and an on-failure destination (SQS/SNS/EventBridge/Lambda/S3). Destinations carry the
full request and error context; prefer them to the legacy DLQ setting.
- EventBridge rules can also retry and use their own DLQ before Lambda's async queue.
- EventBridge Scheduler (not cron rules) for one-off and timezone-aware schedules.
## Sample events
Generate realistic test events instead of hand-writing them:
```bash
sam local generate-event sqs receive-message --body '{"orderId":"o1"}' > events/sqs.json
sam local generate-event apigateway http-api-proxy > events/http.json
sam local generate-event s3 put --bucket my-bucket --key uploads/a.png > events/s3.json
```
Powertools also ships typed event classes (Python `aws_lambda_powertools.utilities.data_classes`,
TypeScript parser schemas) for parsing and validating each source.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.