Claude Code AWS Lambda Durable Functions CLAUDE.md Serverless AI Coding 2026

AWS Built Lambda Durable Functions in 2026. Your CLAUDE.md Hasn't Caught Up

The Prompt Shelf ·

AWS Lambda durable functions shipped in December 2025 and expanded fast through 2026 — Java support went GA in April, .NET in July, customer-managed-key encryption in July, and the feature is now live in roughly 31 regions. It’s a genuinely new execution model: a Lambda function that can pause for up to a year, resume without losing state, and retry a multi-step workflow without you wiring up Step Functions or a hand-rolled DynamoDB state table. It’s also a model with a hard rule that’s easy to violate by accident — your handler code gets replayed from the top on every resume, and anything non-deterministic in that replay path produces silently wrong results, not an error.

That’s a bad combination for an unguided coding agent. Claude Code has no built-in reason to know that time.time(), uuid.uuid4(), or a bare requests.get() behave differently inside a durable handler than inside a normal one — the code looks identical, compiles, and runs correctly on the first invocation. The bug only shows up on replay, which is exactly the kind of failure that’s expensive to trace back to “an agent added this six weeks ago and it worked in every test that didn’t span a checkpoint boundary.”

The gallery’s existing AWS Lambda serverless rules are a solid baseline for classic Lambda hygiene — single-responsibility handlers, initializing SDK clients outside the handler, Powertools logging — but they’re a Python-specific .mdc file written for CDK v2 projects, and they predate durable functions entirely. This is the layer that sits on top: what CLAUDE.md needs to say once a Lambda function in the project uses the durable execution model, plus the general 2026 updates (SnapStart’s language coverage, Graviton, current runtimes) the older rule set doesn’t mention.

Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.

Why “Add Retry Logic to This Lambda” Is Now an Ambiguous Request

Before durable functions, “add retries and multi-step handling to this Lambda” had a small number of answers: hand-roll idempotency with a DynamoDB conditional write, put a queue in front of it, or graduate to Step Functions. In 2026, there’s a fourth answer that lives inside the function’s own code, and an agent with no project-level guidance will guess based on whatever’s most common in its training data — which, for anything durable-function-shaped, is going to default to Step Functions or a manual retry loop, because durable functions are barely a year old at the time of writing.

Left unguided, Claude Code tends toward one of these failure modes on a durable handler:

  • Reading the clock, generating an ID, or calling an external API directly in the handler body, instead of wrapping it in a @durable_step (Python) or context.step() (Node.js) call — this passes every test on first execution and produces a different value on replay
  • Accumulating state in a local variable across steps (total += price in a loop) instead of returning the accumulated value from a step — replay resets the variable to its initial value while the step calls themselves return cached results, so the final total is wrong in a way no exception ever surfaces
  • Branching on a value read outside a step (a feature flag, an environment variable, Date.now()) — if that value could differ between the original invocation and a replay weeks later, the function can take a different code path on resume than it did on first execution
  • Wiring the function up through $LATEST instead of a versioned ARN or alias — an in-flight durable execution is pinned to the code version it started with, and pointing at $LATEST means a deploy mid-execution can leave the checkpoint log referencing code that no longer exists
  • Skipping a timeout on wait_for_callback — without one, a callback waits for the external system to respond for up to the full execution timeout, which can be a year, silently holding a workflow open indefinitely

None of these fail a lint check or a unit test that only exercises a single invocation. They’re the kind of bug that surfaces the first time a real workflow spans a checkpoint boundary in production — which is also the point at which it’s hardest to debug.

Deciding Which Execution Model You’re Even In

The first thing CLAUDE.md needs to settle isn’t a durable-function rule at all — it’s which of four execution models a given Lambda actually uses, because the rules that follow only apply to one of them:

Plain LambdaLambda + SQS/EventBridgeDurable FunctionsStep Functions
State across invocationsNoneNone (queue holds retry state)Automatic checkpoint/replayExplicit state machine
Max duration15 min15 min per attemptUp to 1 year (suspended time is free)Up to 1 year
Where the workflow logic livesOne function, one callSplit across function + queue configOne function, plain codeJSON/YAML state machine definition
Best fitSimple request/responseFire-and-forget with retry via DLQCode-centric multi-step logic tightly coupled to one team’s codebaseCross-service orchestration, 220+ native AWS integrations, visual audit trail
Constraints to know15-minute hard ceilingNo shared state between attempts3,000 operations / 100 MB checkpointed state per executionNo code-level determinism rules, but more moving infrastructure

If the project’s workflows are simple and self-contained, durable functions add complexity for no benefit — a plain handler or a queue is still the right default. Durable functions earn their place when a workflow needs to pause for human approval, wait on a slow downstream process, or coordinate several steps of business logic that all live in the same codebase and don’t need Step Functions’ cross-service integrations or visual state machine. CLAUDE.md should state which model each function or family of functions uses — Claude Code has no way to infer this from an event trigger definition alone, and a durable handler with no durable-specific rules stated will just get treated like a regular one.

The Determinism Rules, With the Failure Mode Each One Prevents

These are the rules that don’t exist in older Lambda guidance because the execution model they protect against didn’t exist before December 2025:

# WRONG — non-deterministic call inside the handler body.
# Runs fine on first invocation; returns a different value on replay.
@durable_execution
def lambda_handler(event, context: DurableContext):
    timestamp = time.time()
    transaction_id = str(uuid.uuid4())
    status = requests.get("https://api.example.com/status").json()
    ...

# RIGHT — every non-deterministic operation is wrapped in a step,
# whose result is checkpointed and replayed verbatim, never re-executed.
@durable_step
def get_timestamp(step_context):
    return time.time()

@durable_step
def generate_transaction_id(step_context):
    return str(uuid.uuid4())

@durable_step
def fetch_status(step_context):
    return requests.get("https://api.example.com/status").json()

@durable_execution
def lambda_handler(event, context: DurableContext):
    timestamp = context.step(get_timestamp())
    transaction_id = context.step(generate_transaction_id())
    status = context.step(fetch_status())
    ...

The three categories worth stating explicitly and separately in CLAUDE.md, because an agent won’t infer the second and third from the first:

  • Time, randomness, and I/O go in steps. time.time(), datetime.now(), uuid.uuid4(), any network call, any read from a source that could change — all of it, every time, no exceptions for values that “probably won’t matter.”
  • State accumulated across steps must come from step return values, not local variables mutated in a loop. A local variable resets on every replay; a step’s return value is checkpointed and comes back exactly as it was the first time.
  • Configuration read at runtime must be captured in a step at the start of execution, not read fresh each time the handler resumes — an environment variable or feature flag that changes between the original invocation and a replay weeks later is a source of the same bug as a bare timestamp.

Two more rules that aren’t about determinism but come up constantly in review:

  • Step bodies are at-least-once, not exactly-once. If an invocation is interrupted before a step’s result is checkpointed, that step re-runs. Anything with a side effect — a charge, a write, an email send — needs its own idempotency key inside the step, the same discipline classic Lambda-plus-SQS handlers already need for at-least-once delivery.
  • Every wait_for_callback needs an explicit timeout. Without one, a callback waits for the external system for up to the full execution timeout — which can be a year — with no way to force it forward.

A CLAUDE.md Template That Distinguishes Durable From Classic

# CLAUDE.md — AWS Lambda Project

## Stack
- Runtime: {Python 3.13/3.14 / Node.js 22/24 / Java 17/21/25}
- IaC: {SAM / CDK v2 / Serverless Framework / Terraform} — state this explicitly.
  The official Claude Code AWS Serverless plugin defaults to SAM MCP tools
  (`sam_init`, `sam_build`, `sam_deploy`); if this project uses CDK or Terraform,
  say so here or Claude Code may reach for `sam` commands that don't apply.
- Architecture: {plain Lambda / Lambda + SQS+EventBridge / durable functions / Step Functions}
  — see the decision table below before adding a new function; don't default
  to durable functions for something a plain handler already covers.

## Classic Lambda Rules (apply to every function, durable or not)
- Initialize SDK clients, DB connections, and heavy imports at module scope,
  never inside the handler — the execution environment reuses them across
  invocations, and initializing per-invocation defeats that.
- One function, one responsibility. Decompose a multi-step workflow into
  a durable function or a Step Functions state machine, not a single
  handler with an internal router.
- Structured JSON logging via `aws-lambda-powertools`, not bare `print()`
  or `console.log()` — CloudWatch Logs Insights queries depend on it.
- Secrets and per-environment config come from Secrets Manager or SSM
  Parameter Store, never hardcoded or committed as plaintext env values.
- Set the function timeout to what the workload actually needs — the
  3-second default is too short for almost everything except a trivial
  health check, but "set it to 900 seconds and forget it" hides real bugs.
- SnapStart is available for Java, Python, and .NET (not Node.js) and cuts
  70-90% of init latency for latency-sensitive cold starts; Provisioned
  Concurrency is the fallback for Node.js or for traffic patterns SnapStart
  doesn't fit. Don't reach for Provisioned Concurrency by default — it has
  a standing cost, SnapStart usually doesn't.

## Durable Function Rules (functions using @durable_execution / withDurableExecution)
- Every read of the clock, a random value, or an external system inside
  the handler body must be wrapped in a step (`@durable_step` in Python,
  `context.step()` in Node.js). No exceptions — this is the rule that
  protects against silently-wrong replay behavior, not a style preference.
- State accumulated across multiple steps is passed through step return
  values, never through a local variable mutated across step calls.
- Branching decisions based on a value that could differ between the
  original run and a replay (feature flags, env vars, timestamps) must
  read that value inside a step at the point the decision is made.
- Step bodies are at-least-once — anything with a side effect needs an
  idempotency key inside the step itself.
- Every `wait_for_callback` sets an explicit timeout. No open-ended waits.
- Functions are invoked through a versioned ARN or alias, never `$LATEST`
  — an in-flight execution is pinned to the version it started with.
- Stay under 3,000 durable operations and 100 MB of checkpointed state
  per execution — one step per side effect, and return references
  (an S3 key, an ID) instead of large payloads from steps.

## Testing
- Durable function unit tests run step functions directly as plain
  functions — don't spin up an actual durable execution for logic tests.
- Replay behavior (does the workflow resume correctly from a mid-execution
  checkpoint) is an integration-test concern, tested against a real or
  emulated durable execution, not asserted from unit tests alone.

settings.json: Letting the Agent Deploy Freely, Denying What Can’t Be Undone

The commands worth gating are the ones that touch a live execution or a production account, not IaC tooling in general:

{
  "permissions": {
    "allow": [
      "Bash(sam build:*)",
      "Bash(sam local invoke:*)",
      "Bash(cdk synth:*)",
      "Bash(cdk diff:*)",
      "Bash(aws lambda get-function:*)",
      "Bash(aws lambda list-versions-by-function:*)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "deny": [
      "Bash(aws lambda delete-function*)",
      "Bash(cdk destroy*)",
      "Bash(sam delete*)",
      "Bash(aws lambda send-durable-execution-callback-failure*)",
      "Bash(aws lambda update-function-code* --function-name *prod*)"
    ]
  }
}

The AWS-maintained Claude Code serverless plugin ships its own PostToolUse hook that runs sam validate after a template edit — worth replicating even without the plugin installed, extended to whichever IaC tool the project actually uses:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATH\" == *template.yaml || \"$CLAUDE_FILE_PATH\" == *.ts ]]; then (sam validate --lint 2>/dev/null || cdk synth --quiet 2>/dev/null); fi"
          }
        ]
      }
    ]
  }
}

What AWS’s Own Claude Code Plugin Already Covers (and What It Doesn’t)

AWS shipped an official Claude Code plugin for serverless in 2026 — it activates automatically on Lambda/SAM/CDK/DynamoDB/EventBridge mentions or via /aws-serverless, and pairs an MCP server (sam_init, sam_build, sam_deploy, sam_local_invoke) with skill guides written by practitioners covering cold starts, concurrency, authorizers, and monitoring. A separate community skill covers similar ground for API Gateway and event-driven patterns. Both are genuinely useful, and neither one is a substitute for a project’s own CLAUDE.md:

AWS’s official plugin / community skillsYour project’s CLAUDE.md
ScopeGeneral AWS serverless expertise, applies to any projectThis project’s actual IaC tool, runtime, and architecture choices
Durable functionsNot covered as of this writing — the feature is newer than most published skill contentWhatever rules this guide’s template above gives you
IaC toolMCP tools are SAM-flavored by defaultSays explicitly if the project uses CDK, Terraform, or Serverless Framework instead
Determinism rulesN/A — general Lambda skills predate durable functionsRequired for any function using @durable_execution
Team-specific conventionsNone — it’s a general-purpose pluginNaming, testing conventions, deploy gates specific to this repo

Installing the plugin and writing a CLAUDE.md aren’t competing choices — the plugin gives Claude Code AWS expertise it wouldn’t otherwise have; CLAUDE.md tells it which parts of that expertise apply to this specific codebase, and covers the parts (durable functions, this team’s IaC tool, this project’s naming conventions) that a general-purpose plugin has no way to know.

Common Mistakes to Watch For

Treating a durable function like a regular one because the code “looks normal.” A durable handler’s body reads like ordinary Python or TypeScript — there’s no syntax that visually marks a line as replay-unsafe. The rule has to live in CLAUDE.md because it can’t be inferred from reading the function.

Wrapping an entire block in a step “to be safe” instead of the specific non-deterministic call. Coarser steps aren’t wrong, but a step that does five things and fails halfway through re-runs all five on retry — wrap at the granularity of one side effect, not one function.

Assuming SnapStart applies to a Node.js function because it applies to the project’s Python functions. SnapStart is Java, Python, and .NET only. A Node.js function that needs cold-start mitigation needs Provisioned Concurrency instead, which has a standing cost SnapStart doesn’t.

Pointing an EventBridge rule or API Gateway integration at $LATEST for convenience during development and never fixing it before production. It works right up until a deploy lands while a durable execution is in flight, at which point that execution’s checkpoint log no longer matches the running code.

Reaching for durable functions because they’re the newest option, on a workflow a plain Lambda already handles correctly. The decision table above exists because “newest and most capable” isn’t the same question as “right fit for this workflow” — durable functions add checkpoint/replay semantics that a simple, stateless handler doesn’t need and shouldn’t pay the complexity cost for.


The interesting thing about durable functions isn’t that they’re hard to use — the SDK is a couple of decorators and a different context object. It’s that the failure mode is invisible in a code review and invisible in a test suite that only exercises one invocation, which is exactly the kind of rule an AI coding agent needs written down rather than inferred. Pin the execution model per function, state the determinism rules for anything durable, and the rest of Lambda’s 2026 surface — SnapStart’s actual language coverage, which IaC tool this project uses, the commands that shouldn’t run unattended — follows the same pattern classic serverless CLAUDE.md files already use.

Browse more real AWS, serverless, and framework-specific CLAUDE.md/AGENTS.md examples in our gallery.


FAQ

What are AWS Lambda durable functions? A programming model, generally available since December 2025, that lets a single Lambda function pause for up to a year and resume without losing state. It works by checkpointing the result of each step() call; on resume, the handler re-runs from the top, but completed steps return their cached results instead of re-executing, and only new code actually runs.

Why does replay break code that works fine on the first invocation? Because replay re-runs the entire handler body, not just the part after the last checkpoint. Anything non-deterministic outside a step — a timestamp, a random ID, a network call, a value read from an environment variable — can return a different result on replay than it did originally, and the function has no way to detect that this happened.

Should I use durable functions instead of Step Functions? Use durable functions when the workflow’s logic is tightly coupled to one codebase and doesn’t need cross-service orchestration. Use Step Functions when you’re coordinating multiple AWS services, need Step Functions’ 220-plus native integrations, or want a visual state machine for audit purposes. The two also combine: a state machine can invoke a durable function as one state for a code-heavy segment.

Does AWS’s official Claude Code plugin for serverless replace the need for a CLAUDE.md? No. The plugin gives Claude Code general AWS serverless expertise — cold starts, event sources, monitoring patterns — through skills and an MCP server, but it’s the same plugin regardless of which project it’s installed in. It doesn’t know this project’s IaC tool, whether any function uses durable functions, or this team’s naming and deploy conventions — that’s what a project-specific CLAUDE.md is for.

Is the gallery’s existing AWS Lambda rule set still useful? Yes, as a baseline for classic Lambda hygiene — single-responsibility handlers, initializing clients outside the handler, structured logging, CDK v2 IaC. It’s a static .mdc file written for Python before durable functions existed, so it won’t tell an agent anything about the determinism rules a durable handler needs; use it alongside the durable-specific rules in this guide, not instead of them.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules