Generated banner for AWS Lambda Durable Functions
Generated logo for AWS Lambda Durable Functions

AWS Lambda Durable Functions

AWS LabsOfficialPublished by AWS Labs

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Low risk

From the author

Build resilient, long-running, multi-step applications with AWS Lambda durable functions with automatic state persistence, retry logic, and orchestration for long-running executions. Covers the critical replay model, step operations, wait/callback patterns, error handling with saga pattern, testing with LocalDurableTestRunner. Triggers on phrases like: lambda durable functions, workflow orchestration, state machines, retry/checkpoint patterns, long-running stateful Lambda functions, saga pattern, human-in-the-loop callbacks, and reliable serverless applications.
— the description by AWS Labs

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The AWS Lambda Durable Functions skill provides a framework for orchestrating complex, multi-step applications that require long-running execution, automatic state persistence, and robust error handling. It enables developers to implement patterns like sagas, human-in-the-loop callbacks, and retries while ensuring progress is maintained across interruptions. The skill covers the replay model, step operations, and testing strategies for both TypeScript and Python environments, helping developers build reliable serverless architectures that can run for up to a year.

What it does

  • Orchestrates multi-step serverless workflows with automatic state persistence.
  • Implements complex error handling patterns including sagas and circuit breakers.
  • Supports long-running executions with wait/callback patterns and human-in-the-loop integration.
  • Provides a replay-aware environment to ensure determinism in distributed serverless functions.
  • Offers testing utilities via LocalDurableTestRunner for local development and validation.
  • Enables dynamic step naming and advanced concurrency patterns for AI agents and batch processing.

When to use it

  • When building workflows that need to persist state across multiple Lambda invocations.
  • When implementing long-running processes that require wait times or external system callbacks.
  • When you need robust retry logic and error recovery for distributed serverless tasks.
  • When developing complex state machines or saga-based transaction management in AWS.
  • When creating AI agent workflows that require reasoning and multi-step tool execution.

Advantages

  • Maintains reliable progress for workflows that may run for up to one year.
  • Simplifies complex orchestration logic through a structured replay model.
  • Provides built-in support for idempotent operations and retry strategies.
  • Enables local testing of distributed stateful logic before cloud deployment.
  • Reduces boilerplate code for managing state, timeouts, and error recovery.

Usage examples

“How do I implement a retry strategy for a database update in a durable function?”
The agent provides a code example using context.step with retry options, explaining how to classify errors as transient or permanent to ensure only appropriate operations are retried.
“Show me how to wait for an external callback in a Python durable function.”
The agent generates a Python snippet using context.wait_for_callback, including configuration for timeouts and fallback logic to handle cases where the callback is not received.
“I am getting a non-deterministic error in my Lambda function, how do I fix it?”
The agent explains the replay model, identifies non-deterministic code (like Date.now or API calls) outside of steps, and provides a refactored code block moving those calls into context.step.
“How do I test my durable function locally?”
The agent guides the user to use the LocalDurableTestRunner, providing a template for instantiating the runner with their handler and executing test cases to verify replay behavior.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/awslabs/aws-lambda-durable-functions/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip aws-lambda-durable-functions.zip -d .agents/skills

Documentation by AWS Labs

AWS Lambda durable functions

Build resilient multi-step applications and AI workflows that can execute for up to 1 year while maintaining reliable progress despite interruptions.

Onboarding

Step 1: Validate Prerequisites

Before using AWS Lambda durable functions, verify:

  1. AWS CLI is installed (2.33.22 or higher) and configured:

    aws --version
    aws sts get-caller-identity
  2. Runtime environment is ready:

    • For TypeScript/JavaScript: Node.js 22+ (node --version)

    • For Python: Python 3.11+ (python --version. Note that currently only Lambda runtime environments 3.13+ come with the Durable Execution SDK pre-installed. 3.11 is the min supported Python version by the Durable SDK itself, however, you could use OCI to bring your own container image with your own Python runtime + Durable SDK.)

  3. Deployment capability exists (one of):

    • AWS SAM CLI (sam --version) 1.153.1 or higher

    • AWS CDK (cdk --version) v2.237.1 or higher

    • Direct Lambda deployment access

Step 2: Select language and IaC framework
Language Selection

Default: TypeScript

Override syntax:

  • "use Python" → Generate Python code

  • "use JavaScript" → Generate JavaScript code

When not specified, ALWAYS use TypeScript

IaC framework selection

Default: CDK

Override syntax:

  • "use CloudFormation" → Generate YAML templates

  • "use SAM" → Generate YAML templates

When not specified, ALWAYS use CDK

Error Scenarios
Unsupported Language
  • List detected language

  • State: "Durable Execution SDK is not yet available for [framework]"

  • Suggest supported languages as alternatives

Unsupported IaC Framework
  • List detected framework

  • State: "[framework] might not support Lambda durable functions yet"

  • Suggest supported frameworks as alternatives

Serverless MCP Server Unavailable
  • Inform user: "AWS Serverless MCP not responding"

  • Ask: "Proceed without MCP support?"

  • DO NOT continue without user confirmation

Step 3: Install SDK

For TypeScript/JavaScript:

npm install @aws/durable-execution-sdk-js
npm install --save-dev @aws/durable-execution-sdk-js-testing

For Python:

pip install aws-durable-execution-sdk-python
pip install aws-durable-execution-sdk-python-testing

When to Load Reference Files

Load the appropriate reference file based on what the user is working on:

  • Getting started, basic setup, example, ESLint, or Jest setup -> see getting-started.md

  • Understanding replay model, determinism, or non-deterministic errors -> see replay-model-rules.md

  • Creating steps, atomic operations, or retry logic -> see step-operations.md

  • Waiting, delays, callbacks, external systems, or polling -> see wait-operations.md

  • Parallel execution, map operations, batch processing, or concurrency -> see concurrent-operations.md

  • Error handling, retry strategies, saga pattern, or compensating transactions -> see error-handling.md

  • Advanced error handling, timeout handling, circuit breakers, or conditional retries -> see advanced-error-handling.md

  • Testing, local testing, cloud testing, test runner, or flaky tests -> see testing-patterns.md

  • Deployment, CloudFormation, CDK, SAM, log groups, deploy, or infrastructure -> see deployment-iac.md

  • Advanced patterns, GenAI agents, completion policies, step semantics, or custom serialization -> see advanced-patterns.md

  • troubleshooting, stuck execution, failed execution, debug execution ID, execution history, execution error, why did my execution fail, execution timed out, callback not received, diagnose execution, or root cause execution -> see troubleshooting-executions.md

Quick Reference

Basic Handler Pattern

TypeScript:

import { withDurableExecution, DurableContext } from '@aws/durable-execution-sdk-js';

export const handler = withDurableExecution(async (event, context: DurableContext) => {
  const result = await context.step('process', async () => processData(event));
  return result;
});

Python:

from aws_durable_execution_sdk_python import durable_execution, DurableContext

@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
    result = context.step(lambda _: process_data(event), name='process')
    return result
Critical Rules
  1. All non-deterministic code MUST be in steps (Date.now, Math.random, API calls)

  2. Cannot nest durable operations - use runInChildContext to group operations

  3. Closure mutations are lost on replay - return values from steps

  4. Side effects outside steps repeat - use context.logger (replay-aware)

Python API Differences

The Python SDK differs from TypeScript in several key areas:

  • Steps: Use @durable_step decorator + context.step(my_step(args)), or inline context.step(lambda _: ..., name='...'). Prefer the decorator for automatic step naming.

  • Wait: context.wait(duration=Duration.from_seconds(n), name='...')

  • Exceptions: ExecutionError (permanent), InvocationError (transient), CallbackError (callback failures)

  • Testing: Use DurableFunctionTestRunner class directly - instantiate with handler, use context manager, call run(input=...)

Invocation Requirements

Durable functions require qualified ARNs (version, alias, or $LATEST):

# Valid
aws lambda invoke --function-name my-function:1 output.json
aws lambda invoke --function-name my-function:prod output.json

# Invalid - will fail
aws lambda invoke --function-name my-function output.json

IAM Permissions

Your Lambda execution role MUST have the AWSLambdaBasicDurableExecutionRolePolicy managed policy attached. This includes:

  • lambda:CheckpointDurableExecution - Persist execution state

  • lambda:GetDurableExecutionState - Retrieve execution state

  • CloudWatch Logs permissions

Additional permissions needed for:

  • Durable invokes: lambda:InvokeFunction on target function ARNs

  • External callbacks: Systems need lambda:SendDurableExecutionCallbackSuccess and lambda:SendDurableExecutionCallbackFailure

Validation Guidelines

When writing or reviewing durable function code, ALWAYS check for these replay model violations:

  1. Non-deterministic code outside steps: Date.now(), Math.random(), UUID generation, API calls, database queries must all be inside steps

  2. Nested durable operations in step functions: Cannot call context.step(), context.wait(), or context.invoke() inside a step function — use context.runInChildContext() instead

  3. Closure mutations that won't persist: Variables mutated inside steps are NOT preserved across replays — return values from steps instead

  4. Side effects outside steps that repeat on replay: Use context.logger for logging (it is replay-aware and deduplicates automatically)

When implementing or modifying tests for durable functions, ALWAYS verify:

  1. All operations have descriptive names

  2. Tests get operations by NAME, never by index

  3. Replay behavior is tested with multiple invocations

  4. Use LocalDurableTestRunner for local testing

MCP Server Configuration

Write access is enabled by default. The plugin ships with --allow-write in .mcp.json, so the MCP server can create projects, generate IaC, and deploy on behalf of the user.

Access to sensitive data (like Lambda and API Gateway logs) is not enabled by default. To grant it, add --allow-sensitive-data-access to .mcp.json.

Resources

Files

references/testing-patterns.md

Testing Patterns

Test durable functions locally and in the cloud with comprehensive test runners.

Critical Testing Patterns

ALWAYS follow these patterns to avoid flaky tests:

DO:
  • ✅ Name all operations for test reliability

  • ✅ TypeScript: Use runner.getOperation("name") to find operations by name

  • ✅ TypeScript: Use WaitingOperationStatus.STARTED when waiting for callback operations

  • ✅ TypeScript: JSON.stringify callback parameters: sendCallbackSuccess(JSON.stringify(data))

  • ✅ TypeScript: Use skipTime: true in setupTestEnvironment for fast tests

  • ✅ TypeScript: Wrap event data in payload object: runner.run({ payload: { ... } })

  • ✅ TypeScript: Cast getResult() to appropriate type: execution.getResult() as ResultType

  • ✅ Python: Use result.get_step("name") to find step operations by name

  • ✅ Python: Use result.operations to iterate and filter operations by type

  • ✅ Python: Instantiate DurableFunctionTestRunner(handler=my_handler) directly

  • ✅ Python: Use runner.run(input={...}, timeout=10) — note input= not payload

  • ✅ Python: The value of result.result is serialized. Deserialize using the appropriate SerDes or default json deserializer.

DON'T:
  • ❌ Use getOperationByIndex() unless absolutely necessary

  • ❌ Assume operation indices are stable (parallel creates nested operations)

  • ❌ TypeScript: Send objects to sendCallbackSuccess — stringify first

  • ❌ TypeScript: Forget that callback results are JSON strings — parse them

  • ❌ TypeScript: Test callbacks without proper synchronization (leads to race conditions)

  • ❌ Python: Confuse DurableFunctionTestRunner (local) with DurableFunctionCloudTestRunner (cloud)

  • ❌ Python: Forget the with runner: context manager — it manages execution lifecycle

Local Testing Setup

TypeScript:

import {
  LocalDurableTestRunner,
  OperationType,
  OperationStatus
} from '@aws/durable-execution-sdk-js-testing';

describe('My Durable Function', () => {
  beforeAll(() => 
    LocalDurableTestRunner.setupTestEnvironment({ skipTime: true })
  );
  
  afterAll(() => 
    LocalDurableTestRunner.teardownTestEnvironment()
  );

  it('should execute workflow', async () => {
    const runner = new LocalDurableTestRunner({ 
      handlerFunction: handler 
    });
    
    const execution = await runner.run({ 
      payload: { userId: '123' } 
    });

    expect(execution.getStatus()).toBe('SUCCEEDED');
    expect(execution.getResult()).toEqual({ success: true });
  });
});

Python:

The Python testing SDK provides DurableFunctionTestRunner for local testing and DurableFunctionCloudTestRunner for cloud testing.

Install the testing SDK:

pip install aws-durable-execution-sdk-python-testing pytest

Example test:

from aws_durable_execution_sdk_python_testing import DurableFunctionTestRunner
from aws_durable_execution_sdk_python.execution import InvocationStatus
from src.my_function import handler

def test_workflow():
    """Test durable function locally."""
    runner = DurableFunctionTestRunner(handler=handler)
    
    with runner:
        result = runner.run(input={'user_id': '123'}, timeout=10)

    assert result.status is InvocationStatus.SUCCEEDED

Getting Operations

CRITICAL: Always get operations by NAME, not by index.

TypeScript:

it('should execute steps in order', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });
  await runner.run({ payload: { test: true } });

  // ✅ CORRECT: Get by name
  const fetchStep = runner.getOperation('fetch-user');
  expect(fetchStep.getType()).toBe(OperationType.STEP);
  expect(fetchStep.getStatus()).toBe(OperationStatus.SUCCEEDED);

  const processStep = runner.getOperation('process-data');
  expect(processStep.getStatus()).toBe(OperationStatus.SUCCEEDED);

  // ❌ WRONG: Get by index (brittle, breaks easily)
  // const step1 = runner.getOperationByIndex(0);
});

Python:

from aws_durable_execution_sdk_python.lambda_service import OperationType
def test_steps_execute():
    """Test step execution."""
    runner = DurableFunctionTestRunner(handler=handler)
    
    with runner:
        result = runner.run(input={'test': True}, timeout=10)

    # ✅ CORRECT: Get step by name
    fetch_step = result.get_step('fetch-user')
    assert fetch_step is not None

    # ✅ Also valid: filter result.operations by type
    step_names = {op.name for op in result.operations if op.operation_type == OperationType.STEP}
    assert step_names >= {'fetch-user', 'process-data'}
    assert 'process-data' in step_names

Testing Replay Behavior

TypeScript:

it('should handle replay correctly', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });
  
  // First execution
  const execution1 = await runner.run({ payload: { value: 42 } });
  expect(execution1.getStatus()).toBe('SUCCEEDED');

  // Simulate replay
  const execution2 = await runner.run({ payload: { value: 42 } });
  expect(execution2.getStatus()).toBe('SUCCEEDED');
  
  // Results should be identical
  expect(execution1.getResult()).toEqual(execution2.getResult());
});

Testing with Fake Clock

TypeScript:

it('should wait for specified duration', async () => {
  const runner = new LocalDurableTestRunner({ 
    handlerFunction: handler 
  });

  const executionPromise = runner.run({ payload: {} });

  // Advance time by 60 seconds
  await runner.skipTime({ seconds: 60 });

  const execution = await executionPromise;
  expect(execution.getStatus()).toBe('SUCCEEDED');

  const waitOp = runner.getOperation('delay');
  expect(waitOp.getType()).toBe(OperationType.WAIT);
  expect(waitOp.getWaitDetails()?.waitSeconds).toBe(60);
});

Test Runner API Patterns

CRITICAL: Always wrap event data in payload and cast results appropriately.

TypeScript:

it('should use correct test runner API', async () => {
  const runner = new LocalDurableTestRunner({
    handlerFunction: handler,
  });

  // ✅ CORRECT: Wrap event in payload
  const execution = await runner.run({
    payload: { name: 'Alice', userId: '123' }
  });

  // ✅ CORRECT: Type cast result
  const result = execution.getResult() as {
    greeting: string;
    message: string;
  };

  expect(result.greeting).toBe('Hello, Alice!');

  // ✅ CORRECT: Get operations by name
  const greetingStep = runner.getOperation('generate-greeting');
  expect(greetingStep.getStepDetails()?.result).toBe('Hello, Alice!');
});

// ❌ WRONG: Missing payload wrapper and type casting
it('incorrect api usage', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });

  // ❌ Missing payload wrapper
  const execution = await runner.run({ name: 'Alice' });

  // ❌ No type casting - result is 'unknown'
  const result = execution.getResult();
  // expect(result.greeting).toBe('...'); // Type error!
});

Testing Callbacks

CRITICAL: Use waitForData() with WaitingOperationStatus.STARTED to avoid flaky tests caused by promise races.

TypeScript:

import { WaitingOperationStatus } from '@aws/durable-execution-sdk-js-testing';

it('should handle callback success', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });

  // Start execution (will pause at callback)
  const executionPromise = runner.run({
    payload: { approver: '[email protected]' }
  });

  // ✅ CRITICAL: Get operation by NAME
  const callbackOp = runner.getOperation('wait-for-approval');

  // ✅ CRITICAL: Wait for operation to reach STARTED status
  await callbackOp.waitForData(WaitingOperationStatus.STARTED);

  // ✅ CRITICAL: Must JSON.stringify callback data!
  await callbackOp.sendCallbackSuccess(
    JSON.stringify({ approved: true, comments: 'Looks good' })
  );

  const execution = await executionPromise;
  expect(execution.getStatus()).toBe('SUCCEEDED');

  // ✅ CRITICAL: Parse JSON string result
  const result: any = execution.getResult();
  const approval = typeof result.approval === 'string'
    ? JSON.parse(result.approval)
    : result.approval;

  expect(approval.approved).toBe(true);
  expect(approval.comments).toBe('Looks good');
});

it('should handle callback failure', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });

  const executionPromise = runner.run({ payload: {} });

  await new Promise(resolve => setTimeout(resolve, 100));

  const callbackOp = runner.getOperation('wait-for-approval');
  
  // Send callback failure
  await callbackOp.sendCallbackFailure(
    'ApprovalDenied',
    'Request was rejected'
  );

  const execution = await executionPromise;
  expect(execution.getStatus()).toBe('FAILED');
});

Python:

Testing callbacks in Python follows the same marker pattern. The callback operation appears in result.operations with operation_type == OperationType.CALLBACK:

def test_callback_creation():
    """Test that callback is created correctly."""
    runner = DurableFunctionTestRunner(handler=handler)
    
    with runner:
        result = runner.run(input={'approver': '[email]'}, timeout=10)

    # Find callback operations in the result
    callback_ops = [
        op for op in result.operations
        if op.operation_type == OperationType.CALLBACK
    ]
    assert len(callback_ops) == 1
    assert callback_ops[0].name == 'wait-for-approval'
    assert callback_ops[0].callback_id is not None

Testing Callback Heartbeats

TypeScript:

it('should handle callback heartbeats', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });

  const executionPromise = runner.run({ payload: {} });

  await new Promise(resolve => setTimeout(resolve, 100));

  const callbackOp = runner.getOperation('long-running-process');
  
  // Send heartbeats
  await callbackOp.sendCallbackHeartbeat();
  await runner.skipTime({ minutes: 2 });
  await callbackOp.sendCallbackHeartbeat();
  await runner.skipTime({ minutes: 2 });
  
  // Complete callback
  await callbackOp.sendCallbackSuccess(JSON.stringify({ status: 'completed' }));

  const execution = await executionPromise;
  expect(execution.getStatus()).toBe('SUCCEEDED');
});

Testing Error Scenarios

TypeScript:

it('should retry on failure', async () => {
  let attemptCount = 0;
  
  const testHandler = withDurableExecution(async (event, context: DurableContext) => {
    return await context.step('flaky-operation', async () => {
      attemptCount++;
      if (attemptCount < 3) {
        throw new Error('Temporary failure');
      }
      return { success: true };
    });
  });

  const runner = new LocalDurableTestRunner({ handlerFunction: testHandler });
  const execution = await runner.run({ payload: {} });

  expect(execution.getStatus()).toBe('SUCCEEDED');
  expect(attemptCount).toBe(3);

  const step = runner.getOperation('flaky-operation');
  expect(step.getStatus()).toBe(OperationStatus.SUCCEEDED);
});

it('should fail after max retries', async () => {
  const testHandler = withDurableExecution(async (event, context: DurableContext) => {
    return await context.step(
      'always-fails',
      async () => {
        throw new Error('Permanent failure');
      },
      {
        retryStrategy: createRetryStrategy({ maxAttempts: 3 })
      }
    );
  });

  const runner = new LocalDurableTestRunner({ handlerFunction: testHandler });
  const execution = await runner.run({ payload: {} });

  expect(execution.getStatus()).toBe('FAILED');
  expect(execution.getError()?.errorMessage).toContain('Permanent failure');
});

Testing Concurrent Operations

TypeScript:

it('should process items concurrently', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });
  
  const execution = await runner.run({ 
    payload: { items: [1, 2, 3, 4, 5] } 
  });

  expect(execution.getStatus()).toBe('SUCCEEDED');

  const mapOp = runner.getOperation('process-items');
  expect(mapOp.getType()).toBe(OperationType.MAP);

  // Check individual item operations
  const item0 = runner.getOperation('process-0');
  expect(item0.getStatus()).toBe(OperationStatus.SUCCEEDED);
});

Cloud Testing

For integration tests against real Lambda:

TypeScript:

import { CloudDurableTestRunner } from '@aws/durable-execution-sdk-js-testing';

describe('Integration Tests', () => {
  it('should execute in real Lambda', async () => {
    const runner = new CloudDurableTestRunner({
      functionName: 'my-durable-function:1',  // Qualified ARN required
      client: new LambdaClient({ region: 'us-east-1' })
    });

    const execution = await runner.run({
      payload: { userId: '123' },
      config: { pollInterval: 1000 }
    });

    expect(execution.getStatus()).toBe('SUCCEEDED');
    
    const step = runner.getOperation('fetch-user');
    expect(step.getStatus()).toBe(OperationStatus.SUCCEEDED);
  });
});

Python:

Cloud mode uses DurableFunctionCloudTestRunner with the same API:

# Set environment variables for cloud mode
export AWS_REGION=us-west-2
export QUALIFIED_FUNCTION_NAME="my-durable-function:$LATEST"
export LAMBDA_FUNCTION_TEST_NAME="my_function"

# Run in cloud mode
pytest --runner-mode=cloud -k test_workflow

The same test works in both modes:

def test_workflow_cloud():
    """Test against deployed Lambda function."""
    runner = DurableFunctionCloudTestRunner(
        function_name='my-function:$LATEST',
        region='us-west-2'
    )
    
    with runner:
        result = runner.run(input={'user_id': '123'}, timeout=60)

    assert result.status is InvocationStatus.SUCCEEDED

Test Assertions

TypeScript:

it('should validate operation details', async () => {
  const runner = new LocalDurableTestRunner({ handlerFunction: handler });
  await runner.run({ payload: {} });

  const step = runner.getOperation('process-data');
  
  // Check operation type
  expect(step.getType()).toBe(OperationType.STEP);
  
  // Check status
  expect(step.getStatus()).toBe(OperationStatus.SUCCEEDED);
  
  // Check timing
  expect(step.getStartTimestamp()).toBeDefined();
  expect(step.getEndTimestamp()).toBeDefined();
  
  // Check result
  const stepDetails = step.getStepDetails();
  expect(stepDetails?.result).toEqual({ processed: true });
});

Best Practices

  1. Always name operations for reliable test assertions

  2. Get operations by name, never by index

  3. Test replay behavior with multiple invocations

  4. Use fake clock for time-dependent tests

  5. Test error scenarios including retries and failures

  6. Test callbacks with success, failure, and timeout cases

  7. Validate operation details (type, status, timing, results)

  8. Use cloud tests for integration testing

  9. Mock external dependencies in unit tests

  10. Test concurrent operations individually and as a group

Common Pitfalls

❌ Getting Operations by Index
// Brittle - breaks when operations change
const step = runner.getOperationByIndex(0);
✅ Getting Operations by Name
// Robust - works even if operation order changes
const step = runner.getOperation('fetch-user');
❌ Not Waiting for Callbacks
// Race condition - callback might not exist yet
const callbackOp = runner.getOperation('wait-approval');
await callbackOp.sendCallbackSuccess('{}');
✅ Waiting for Callbacks
// Use waitForData with proper status
import { WaitingOperationStatus } from '@aws/durable-execution-sdk-js-testing';

const callbackOp = runner.getOperation('wait-approval');
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
await callbackOp.sendCallbackSuccess(JSON.stringify({}));

Common Testing Errors

TypeScript
ErrorCauseSolution
'result' is of type 'unknown'Missing type casting in testsCast result: as any or specific type
'payload' does not exist in typeWrong test runner APIWrap event in payload: {} object
Cannot find operation at indexUsing index for unstable operationsUse getOperation("name") instead
Flaky callback testsRace condition with callback creationUse waitForData(WaitingOperationStatus.STARTED)
Unexpected token in callback resultForgot to JSON.stringifyAlways stringify: JSON.stringify(data)
Callback result parsing errorResult is JSON stringParse result: JSON.parse(result.value)
Operation not found by nameMissing operation nameAlways name operations in handler

Jest Configuration

jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/*.test.ts'],
  transform: {
    '^.+\\.ts$': 'ts-jest',
  },
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
  ],
};

Key points:

  • preset: 'ts-jest' is essential for TypeScript support

  • transform maps .ts files to ts-jest transformer

  • testMatch specifies test file patterns

  • Use skipTime: true in test setup for fast execution

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs a command-line tool, Needs Python, Needs Node.js

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
AWS Labs
Publisher
AWS Labs (Official)
Source
awslabs/agent-plugins · plugins/aws-serverless/skills/aws-lambda-durable-functions · commit fbefb37
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • NOTICE (added by AutoGineer from NOTICE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by AWS Labs.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download aws-lambda-durable-functions.zip