AWS Step Functions orchestrates Lambda functions, ECS tasks, SQS messages, and API calls into visual, auditable workflows. Each execution step is tracked — you can see exactly where a workflow is, what inputs it received, and why it failed. Step Functions handles retries, timeouts, parallel branching, and human approval steps without you managing the coordination logic. Claude Code writes Amazon States Language (ASL) definitions, CDK constructs that compose state machines, and the error handling patterns that prevent silent infinite retries.
CLAUDE.md for Step Functions Projects
## Workflow Stack
- AWS Step Functions with Express Workflows (high-volume, short) or Standard (auditable, long)
- Use Standard for: order processing, onboarding, anything needing audit trail
- Use Express for: high-volume, sub-5-minute, idempotent pipelines (data transforms)
- Error handling: retry with exponential backoff + jitter; catch specific error types
- CDK: use aws-cdk-lib/aws-stepfunctions + tasks module
- Lambda functions called by SF: always idempotent; SF will retry on failure
- Timeout: set HeartbeatSeconds on long-running tasks; state TimeoutSeconds as backstop
State Machine Definition (ASL)
{
"Comment": "Order fulfillment workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:validate-order",
"Retry": [{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2,
"JitterStrategy": "FULL"
}],
"Catch": [{
"ErrorEquals": ["ValidationError"],
"Next": "NotifyValidationFailed",
"ResultPath": "$.error"
}],
"Next": "ChargePayment"
},
"ChargePayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "charge-payment",
"Payload": {
"orderId.$": "$.orderId",
"amountCents.$": "$.amountCents",
"paymentMethodId.$": "$.paymentMethodId"
}
},
"ResultPath": "$.payment",
"Retry": [{
"ErrorEquals": ["Lambda.ServiceException"],
"IntervalSeconds": 1,
"MaxAttempts": 2,
"BackoffRate": 2
}],
"Catch": [{
"ErrorEquals": ["PaymentDeclined"],
"Next": "HandlePaymentDeclined",
"ResultPath": "$.error"
}],
"Next": "FulfillInParallel"
},
"FulfillInParallel": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "ReserveInventory",
"States": {
"ReserveInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:reserve-inventory",
"End": true
}
}
},
{
"StartAt": "CreateShipment",
"States": {
"CreateShipment": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:create-shipment",
"End": true
}
}
}
],
"ResultPath": "$.fulfillment",
"Next": "WaitForShipping"
},
"WaitForShipping": {
"Type": "Wait",
"Seconds": 86400,
"Next": "CheckShipmentStatus"
},
"CheckShipmentStatus": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:check-shipment",
"Next": "IsDelivered"
},
"IsDelivered": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.shipment.status",
"StringEquals": "DELIVERED",
"Next": "OrderComplete"
},
{
"Variable": "$.shipment.status",
"StringEquals": "FAILED",
"Next": "HandleShippingFailure"
}
],
"Default": "WaitForShipping"
},
"OrderComplete": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:send-confirmation",
"End": true
},
"NotifyValidationFailed": { "Type": "Fail", "Error": "ValidationFailed" },
"HandlePaymentDeclined": { "Type": "Fail", "Error": "PaymentDeclined" },
"HandleShippingFailure": { "Type": "Fail", "Error": "ShippingFailed" }
}
}
CDK State Machine Definition
// lib/order-workflow.ts — CDK Step Functions
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as cdk from 'aws-cdk-lib';
export function createOrderWorkflow(
scope: cdk.Stack,
fns: { validate: lambda.IFunction; charge: lambda.IFunction; ship: lambda.IFunction },
) {
const validateOrder = new tasks.LambdaInvoke(scope, 'ValidateOrder', {
lambdaFunction: fns.validate,
outputPath: '$.Payload',
retryOnServiceExceptions: true,
});
const chargePayment = new tasks.LambdaInvoke(scope, 'ChargePayment', {
lambdaFunction: fns.charge,
resultPath: '$.payment',
}).addRetry({
errors: ['Lambda.ServiceException', 'Lambda.TooManyRequestsException'],
maxAttempts: 3,
interval: cdk.Duration.seconds(2),
backoffRate: 2,
jitterStrategy: sfn.JitterType.FULL,
}).addCatch(
new sfn.Fail(scope, 'PaymentDeclined', { error: 'PaymentDeclined' }),
{ errors: ['PaymentDeclined'], resultPath: '$.error' }
);
const reserveInventory = new tasks.LambdaInvoke(scope, 'ReserveInventory', {
lambdaFunction: fns.ship,
});
const createShipment = new tasks.LambdaInvoke(scope, 'CreateShipment', {
lambdaFunction: fns.ship,
});
// Parallel: run both in parallel, wait for both to complete
const parallel = new sfn.Parallel(scope, 'FulfillInParallel')
.branch(reserveInventory)
.branch(createShipment);
const definition = validateOrder
.next(chargePayment)
.next(parallel);
return new sfn.StateMachine(scope, 'OrderWorkflow', {
definitionBody: sfn.DefinitionBody.fromChainable(definition),
stateMachineType: sfn.StateMachineType.STANDARD,
timeout: cdk.Duration.hours(24),
tracingEnabled: true,
logs: {
destination: new cdk.aws_logs.LogGroup(scope, 'WorkflowLogs', {
retention: cdk.aws_logs.RetentionDays.TWO_WEEKS,
}),
level: sfn.LogLevel.ERROR,
includeExecutionData: true,
},
});
}
Triggering and Querying Executions
// services/order-service.ts — start and monitor workflow executions
import { SFNClient, StartExecutionCommand, DescribeExecutionCommand } from '@aws-sdk/client-sfn';
const sfn = new SFNClient({ region: 'us-east-1' });
export async function startOrderFulfillment(order: Order): Promise<string> {
const { executionArn } = await sfn.send(new StartExecutionCommand({
stateMachineArn: process.env.ORDER_WORKFLOW_ARN!,
name: `order-${order.id}-${Date.now()}`, // Unique execution name
input: JSON.stringify({
orderId: order.id,
amountCents: order.totalCents,
paymentMethodId: order.paymentMethodId,
items: order.items,
}),
}));
return executionArn;
}
export async function getWorkflowStatus(executionArn: string) {
const { status, input, output, startDate, stopDate } = await sfn.send(
new DescribeExecutionCommand({ executionArn })
);
return { status, startDate, stopDate, output: output ? JSON.parse(output) : null };
}
For the AWS CDK constructs that deploy these Step Functions state machines alongside Lambda and SQS, the AWS CDK guide covers the full CDK application pattern. For the Temporal alternative to Step Functions for complex workflows with code-first definitions, the Temporal workflows guide covers activities, workflows, and durable execution. The Claude Skills 360 bundle includes Step Functions skill sets covering state machine design, parallel branching, error handling, and CDK integration. Start with the free tier to try Step Functions workflow generation.