aws-serverless-patterns
Skill8 starsChanged 41 days agoApache-2.0View on GitHub
AWS Lambda, API Gateway, DynamoDB, Step Functions, EventBridge patterns and best practices. Use when building or reviewing serverless backends, CDK infrastructure, or Lambda functions.
- Installs packages
In this repository17 other files it gives its agents.
- AGENTSAGENTS.md
- amazon-bedrockSkill
- amazon-location-serviceSkill
- amazon-polly-generativeSkill
- aws-architecture-diagramSkill
- aws-diagram-htmlSkill
- aws-diagram-pngSkill
- cdk-infrastructure-patternsSkill
- cognito-email-migrationSkill
- cognito-passkey-authSkill
- deploy-on-awsSkill
- email-template-renderingSkill
- mcp-tool-discoverySkill
- personal-rules-managementSkill
- react-frontend-patternsSkill
- reinvent-stack-toolkitSkill
- testing-patternsSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 50
Always use on-demand billing for variable workloads
- Distinctive: line 66
Always enable X-Ray tracing
- Distinctive: line 67
Use DLQ for failed executions
- Very specific: line 135
Never call any Stop*/terminate API while diagnosing — this is a read/observe-only operation.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AWS Serverless Patterns
When uncertain about an AWS API parameter, IAM action, or service quota, resolve and call the AWS docs-search tool (
search_documentation) from theaws-mcp-serverMCP server — seesteering/aws-agent-toolkit.md→ "Resolving the correct tool name" if the literal tool name doesn't resolve on first try. If unsure which MCP tool covers your task, seeskills/mcp-tool-discovery/SKILL.mdfor the discovery flow.
Lambda Function Template
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.utilities.typing import LambdaContext
logger = Logger()
tracer = Tracer()
metrics = Metrics()
@logger.inject_lambda_context
@tracer.capture_lambda_handler
@metrics.log_metrics
def lambda_handler(event: dict, context: LambdaContext) -> dict:
logger.info("Processing request", extra={"request_id": context.aws_request_id})
metrics.add_metric(name="RequestProcessed", unit="Count", value=1)
return {"statusCode": 200, "body": json.dumps({"status": "success"})}CDK Lambda Pattern
from aws_cdk.aws_lambda_python_alpha import PythonFunction
from aws_cdk.aws_lambda import Runtime, Tracing
PythonFunction(self, 'MyFunction',
runtime=Runtime.PYTHON_3_14,
entry='cdk-backend/lambda/functions/my_function',
index='my_function.py',
tracing=Tracing.ACTIVE,
memory_size=512,
timeout=Duration.seconds(30),
)DynamoDB Single-Table Pattern
- PK:
ENTITY#id(e.g.,USER#123,ORDER#456) - SK:
METADATAfor base item,RELATION#idfor relationships - GSI1PK/GSI1SK for secondary access patterns
- Always use on-demand billing for variable workloads
- Enable point-in-time recovery
API Gateway Pattern
- REST API with Cognito authorizer
- Request validation models at gateway level
- Defense in depth: validate again in Lambda with pydantic
- CORS: explicit origins, never
*in production - Set CORS headers on every Lambda response, not just the OPTIONS preflight — if the frontend calls the API cross-origin (no CloudFront routing to API Gateway), a real 4xx/5xx from application code still needs
Access-Control-Allow-Originetc. on it or the browser reports it as a CORS failure regardless of the actual status code. - A Lambda that throws during cold start (missing import, bad env var, etc.) returns a headerless 502 with no CORS headers at all — this looks exactly like a CORS error in the browser console but isn't one. If a request that used to work suddenly "fails with CORS" after a deploy, check CloudWatch logs for the function first (
Runtime.ImportModuleError, unhandled exception in module-scope code) before touching CORS configuration — CORS headers can't be attached to a response the runtime never got a chance to send.
Step Functions Pattern
- Express workflows for synchronous, high-volume (<5 min)
- Standard workflows for long-running, auditable processes
- Always enable X-Ray tracing
- Use DLQ for failed executions
EventBridge Pattern
- A Lambda invoked as an EventBridge rule target receives the full EventBridge envelope, not the raw event you published.
PutEvents'sDetailfield ends up nested atevent["detail"]on the invocation side — the envelope also carriesversion,id,detail-type,source,account,time,region,resources. Reading a top-level key (e.g.event["itemId"]) directly on a payload that only exists insidedetailraises a bareKeyError— passes every static check and deploys clean, only surfaces the first time the rule actually fires. Unwrap defensively so the same handler also works for direct/local invocation with the flat payload:payload = event.get("detail", event), then read fields offpayload. - Publish one
Source/DetailTypenaming scheme per event family and keep the rule'sEventPatternmatching on both, not justDetailType— an unscoped pattern on a shared bus can match events from other unrelated producers. - An EventBridge rule targeting a Lambda function needs an explicit
AWS::Lambda::Permission(Principal: events.amazonaws.com,SourceArnscoped to the rule's ARN) unless the target's execution role already grantslambda:InvokeFunction— SAM does not add this automatically forAWS::Events::Ruletargets the way it does forEvents:onAWS::Serverless::Function.
Error Response Pattern
return {
"statusCode": 400,
"body": json.dumps({
"error": {"code": "VALIDATION_ERROR", "message": "Invalid input"},
"meta": {"requestId": context.aws_request_id}
})
}Lambda Durable Functions Pattern
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import WaitForCallbackConfig, Duration
@durable_execution
def lambda_handler(event: dict, context: DurableContext) -> dict:
result = context.step(lambda _: do_work(event), name="do-work")
def submit(callback_id: str, _ctx) -> None:
notify_external_system(callback_id)
raw_decision = context.wait_for_callback(
submitter=submit, name="wait-for-decision",
config=WaitForCallbackConfig(timeout=Duration.from_days(1)),
)
return {"result": result}A wait_for_callback() result is the raw payload exactly as the external caller sent it — never auto-parsed. SendDurableExecutionCallbackSuccess's Result field is an opaque bytes/string field, not a structured typed value. If the caller sends Result=json.dumps({...}), the workflow's wait_for_callback() call returns that exact JSON string, not a dict — calling .get(...) on it directly raises AttributeError: 'str' object has no attribute 'get'. Deserialize it yourself, once, immediately after the call returns:
raw_decision = context.wait_for_callback(...)
decision = json.loads(raw_decision) if isinstance(raw_decision, str) else raw_decisionNever call a durable operation (context.step, context.wait, context.invoke) from inside a wait_for_callback submitter — it produces NonDeterministicExecutionError on resume, not at deploy time. The submitter only executes once, when the execution first suspends; on replay after the callback resolves, the SDK expects the exact same operation sequence to reconstruct, and a step nested inside the submitter breaks that sequence. If the submitter needs to persist state (e.g., writing the callback id somewhere an external caller can find it), do that with a plain synchronous call directly in the submitter body — the submitter itself is already checkpointed by wait_for_callback, so it doesn't need, and cannot use, a nested context.step():
# WRONG -- nested step inside the submitter, breaks replay
def submit(callback_id: str, ctx) -> None:
context.step(persist_callback_id(callback_id), name="persist") # NonDeterministicExecutionError
# CORRECT -- plain call, no nested durable operation
def submit(callback_id: str, ctx) -> None:
persist_callback_id_directly(callback_id) # ordinary function call, not context.step(...)A durable execution's callbackId goes stale once that execution terminates (times out, fails, or the callback is already resolved). Any caller invoking SendDurableExecutionCallbackSuccess/Failure against a stale id gets CallbackTimeoutException: The callback is either timed out or already completed — this is a real runtime condition to handle explicitly (mark whatever state referenced that callback as expired/needs-resubmission), not just an error to log and 502 on.
When a durable execution silently doesn't progress, diagnose with get-durable-execution-history before guessing. This read-only AWS CLI call gives an exact step-by-step timeline with the real error payload for every failed step, callback, or child context — far faster than reasoning from CloudWatch log greps:
aws lambda get-durable-execution-history \
--durable-execution-arn <arn> --include-execution-dataNever call any Stop*/terminate API while diagnosing — this is a read/observe-only operation. See the aws-lambda-durable-functions skill's troubleshooting-executions.md reference for the full procedure, including how to map event types (StepFailed, CallbackTimedOut, ChainedInvokeFailed, etc.) to root causes.
SAM Template Gotchas
- Don't set
Metadata.BuildMethodon anAWS::Serverless::LayerVersionthat has norequirements.txt— it double-nests the layer'spython/directory. SAM's Python layer builder treats aBuildMethod-tagged layer as a pip-install target and wraps itsContentUriin an extrapython/on top of whatever structure is already there, producingpython/python/<package>instead ofpython/<package>(which breaks the import). If the layer is pure code with nothing topip install, omitMetadataentirely and lay the source out as<content-uri>/python/<package>/directly — SAM copies it as-is with no extra nesting. - A function cannot reference its own
AutoPublishAliasfrom inside its ownGlobals.Function.Environmentblock (or its own per-functionEnvironment) —!Ref MyFunction.Aliasused as an environment variable onMyFunctionitself is a circular dependency (sam validate --lintcatches it asE3004) because the alias resource depends on the function, and the function's env now depends on the alias. If a different function needs that alias ARN (e.g., to invoke it), put the!Ref MyFunction.Aliasenv var on that other function, never on the function defining the alias.
Known Gotchas (found only by real invocation, not static validation)
These will pass sam validate, a clean build, and a successful deploy every time — they only surface when the deployed function is actually invoked with real input. Check for all of them proactively; do not wait to rediscover them per project.
- A specific Bedrock model ID may not be invokable on-demand. Some models reject direct on-demand invocation by bare model ID with
ValidationException: ... on-demand throughput isn't supported. Retry your request with the ID or ARN of an inference profile. Checkaws bedrock list-inference-profilesfor a matchingus.<model-id>orglobal.<model-id>entry and use that as the Converse/InvokeModelmodelIdinstead — it's the correct invocation identifier for the same model, not a different model, and the fix is to find that identifier, not to substitute a different model or fall back to a workaround. IAM must then grantbedrock:InvokeModel/Converseon both the inference profile ARN and every underlying foundation-model ARN it can route to (aws bedrock get-inference-profile --query 'models[].modelArn'— a cross-region profile routes to multiple regions, each needs its own ARN in the policy). - Bedrock model-specific
inferenceConfigparameter support varies by model generation — don't assume a field liketemperatureis universally accepted. A value that worked on one model can be rejected outright on another withValidationException: ... 'temperature' is deprecated for this model.Remove or adjust the specific offending field rather than assuming the whole call shape is wrong. boto3.resource("dynamodb")rejects native Pythonfloatvalues outright withTypeError: Float types are not supported. Use Decimal types instead.Any numeric value that originated from JSON (an LLM's structured output, an API request body, anyjson.loads) is a plainfloatand will crash aput_item/update_itemcall. Recursively convert floats todecimal.Decimal(str(value))(viastr(), not the float directly, to avoid binary-float imprecision) in a single shared write helper used by every call site — not patched in per call site, since every current and future numeric field is at risk.sam deploy --parameter-overridestruncates a value at the first whitespace, even when the wholeParameterKey=...,ParameterValue="..."argument is shell-quoted. A value containing a space (e.g. anAuthorization: Basic <token>-style header) needs an extra layer of single quotes around the value itself:"ParameterKey=Foo,ParameterValue='value with spaces'". Without it, everything after the first space is silently dropped — no error, exit code 0, a successfully deployed but truncated parameter. Verify withsam deploy --debug ... --no-execute-changeset 2>&1 | grep parameter_overridesbefore trusting any deploy that sets a space-containing parameter.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

