AWS Step Functions skill for Codex

Workflows where every failure has somewhere to go. Codex writes state machines where each Task retries only transient errors, catches named business errors into a compensation path that keeps the original input, and has a timeout. It chooses Standard or Express from duration and delivery guarantees, uses direct service integrations instead of wrapper Lambdas, and tests individual states with the TestState API before deploying.

  • Cloud Platforms
  • $aws-step-functions
  • 204 lines
  • 3 files
  • ~2,250 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- aws-step-functions

Installs to ~/.agents/skills/aws-step-functions/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $aws-step-functions, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and debugs AWS Step Functions state machines in Amazon States Language - Standard vs Express choice, JSONata or JSONPath data flow, Retry with backoff and jitter, Catch and compensation (saga) paths, timeouts and heartbeats, .sync and waitForTaskToken integrations, Map and Distributed Map, plus testing with the TestState API and redriving failed executions. Use when orchestrating multi-step workflows, adding error handling to a state machine, an execution fails or hangs, choosing Express vs Standard, or processing large S3 datasets in parallel.

Use it when

  • Orchestrating several Lambdas, containers or AWS API calls
  • Adding error handling, timeouts or human approval to a workflow
  • An execution fails, hangs or hits the history limit
  • Fanning out over a large S3 dataset

Use something else when

How to check the result

TestState in DEBUG mode shows correct output for success and failure inputs of each changed state, validate-state-machine-definition reports no diagnostics, and a dev execution exercises every new branch.

The mistake it prevents

Catching an error without keeping the original input, so the compensation step receives only the error and cannot undo the order.

~/.agents/skills/aws-step-functions/SKILL.md

--- name: aws-step-functions description: Designs and debugs AWS Step Functions state machines in Amazon States Language - Standard vs Express choice, JSONata or JSONPath data flow, Retry… license: MIT ---

Done well means: every Task has a Retry for transient errors and a Catch that leads to a defined outcome, every wait has a timeout, the workflow type matches its duration and delivery guarantee, payloads stay small, and each state’s behaviour was exercised with TestState before the whole machine was deployed.

Before changing anything

  1. Read AGENTS.md and find the definition: an .asl.json file, a CDK DefinitionBody, a SAM DefinitionUri, or Terraform. Edit it there.
  2. Note the type (STANDARD or EXPRESS), the query language ("QueryLanguage": "JSONata" at the top or per state; JSONPath otherwise), and the role’s permissions.
  3. For a failing workflow, read the failing execution (needs network and credentials):
aws stepfunctions list-executions --state-machine-arn $SM --status-filter FAILED --max-items 5
aws stepfunctions get-execution-history --execution-arn $EXEC --reverse-order --max-items 20 \
  --query 'events[].{t:type,id:id,err:[taskFailedEventDetails.error,executionFailedEventDetails.error],cause:taskFailedEventDetails.cause}'

Standard vs Express

FactorStandardExpress
Max duration1 year5 minutes
SemanticsExactly-once state transitionsAt-least-once (async) or at-most-once (sync)
.sync and waitForTaskTokenYesNo
PricingPer state transitionPer request plus duration and memory
HistoryVisible in console/API for 90 daysCloudWatch Logs only (enable logging)
Redrive failed executionsYesNo
Typical useBusiness processes, human approval, long jobsHigh-volume event processing, API-backing workflows, Distributed Map children

Rule of thumb: anything that waits on people or long jobs, or must not run twice, is Standard. High-volume, short and idempotent is Express. A Standard parent can call Express children to combine both.

Error handling pattern

{
  "QueryLanguage": "JSONata",
  "StartAt": "ChargeCard",
  "States": {
    "ChargeCard": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Arguments": {
        "FunctionName": "arn:aws:lambda:eu-west-1:111111111111:function:charge-card:live",
        "Payload": "{% $states.input %}"
      },
      "Output": "{% $states.result.Payload %}",
      "TimeoutSeconds": 30,
      "Retry": [
        {
          "ErrorEquals": ["Lambda.ServiceException", "Lambda.AWSLambdaException",
                          "Lambda.SdkClientException", "Lambda.TooManyRequestsException"],
          "IntervalSeconds": 1, "MaxAttempts": 3, "BackoffRate": 2, "JitterStrategy": "FULL"
        },
        {
          "ErrorEquals": ["PaymentProviderUnavailable"],
          "IntervalSeconds": 5, "MaxAttempts": 4, "BackoffRate": 2, "MaxDelaySeconds": 60
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["CardDeclined"],
          "Output": "{% $merge([$states.input, {'error': $states.errorOutput}]) %}",
          "Next": "NotifyDeclined"
        },
        {
          "ErrorEquals": ["States.ALL"],
          "Output": "{% $merge([$states.input, {'error': $states.errorOutput}]) %}",
          "Next": "ReleaseInventory"
        }
      ],
      "Next": "ShipOrder"
    }
  }
}

Rules:

  • Retry only errors that can succeed on retry: throttling, service errors, timeouts, your own transient error names. Never retry States.ALL blindly; a validation error retried 3 times is just slower failure.
  • Throw named errors from your code (a Lambda raising class CardDeclined(Exception) surfaces as error CardDeclined) so Retry and Catch can match them precisely.
  • Order matters: the first matching Retry and the first matching Catch win. Put specific errors before States.ALL, which must be last.
  • Keep the original input when catching. In JSONPath use "ResultPath": "$.error"; in JSONata merge $states.errorOutput into the input as above. Without this the catch handler receives only the error and loses the order data.
  • Every Task gets TimeoutSeconds. The default is effectively unlimited, so a hung integration holds a Standard execution open for up to a year.
  • Callback tasks (.waitForTaskToken) also get HeartbeatSeconds, and the worker sends SendTaskHeartbeat while it runs.

Compensation (saga)

For multi-step business transactions, each step that has a side effect gets a matching undo state, and Catch routes to the undo chain for the steps already completed:

ReserveInventory -> ChargeCard -> ShipOrder -> Succeed
       |                |             |
       v                v             v
     Fail     <-  ReleaseInventory <- RefundCard

Compensating actions must be idempotent and retried aggressively; a failed compensation should alert a human (SNS, ticket) rather than fail silently.

Integration patterns

PatternResource suffixUse when
Request-responsearn:aws:states:::sns:publishFire and continue
Run a job and waitarn:aws:states:::ecs:runTask.sync, glue:startJobRun.sync, states:startExecution.sync:2Step Functions polls the job for you
Callbackarn:aws:states:::sqs:sendMessage.waitForTaskTokenHuman approval, external systems; pass $states.context.Task.Token
Any AWS APIarn:aws:states:::aws-sdk:dynamodb:putItemNo optimized integration exists; error names become DynamoDb.ConditionalCheckFailedException style

Call AWS services directly from the state machine instead of wrapping each API call in a Lambda; fewer functions, fewer cold starts, fewer failure points.

Map and Distributed Map

ItemsUse
Up to a few hundred, small, in the state inputInline Map with MaxConcurrency
Thousands to millions, from S3 (CSV, JSON, JSON Lines, object listing)Distributed Map with ItemReader, Express child executions, ResultWriter to S3

Set MaxConcurrency to what downstream systems can take, and ToleratedFailurePercentage so one bad record does not fail a million-item run. Batch items (ItemBatcher) when per-item work is tiny.

Limits that shape designs

  • State input/output payload: 256 KiB at time of writing. Pass S3 keys or IDs, not documents.
  • Standard execution history: 25,000 events. Long loops must hand off to a child execution (or use Distributed Map) before hitting it.
  • Variables (Assign, JSONata or JSONPath) reduce the need to carry everything through every state’s output; prefer them to ever-growing payloads.

Data flow details for both query languages (Arguments/Output/Assign vs InputPath/Parameters/ResultSelector/ResultPath/OutputPath, context object fields and intrinsic functions) are in references/data-flow.md. Read it when writing or converting state input/output mappings.

Testing

  1. Unit-test the code behind each Lambda task normally.
  2. Test single states against the real service with the TestState API (needs network and credentials; it runs the state for real, so use non-production resources):
aws stepfunctions test-state \
  --definition file://states/charge-card.json \
  --role-arn arn:aws:iam::111111111111:role/sfn-test \
  --input '{"orderId":"o-1","amount":42}' \
  --inspection-level DEBUG

DEBUG shows the input and output after every data-flow step, which is how you debug mappings. Test Catch paths by feeding an input that makes the task fail. 3. Validate the full definition before deploying:

aws stepfunctions validate-state-machine-definition --definition file://order.asl.json \
  --query 'diagnostics'
  1. Run an execution in a dev stack and check every branch you changed.

Operations

  • Redrive a failed Standard execution from the failed state after fixing the cause: aws stepfunctions redrive-execution --execution-arn $EXEC. This re-runs side effects from that state onward; confirm with the user first for production.
  • Publish versions and route an alias for safe rollouts of definition changes.
  • Enable CloudWatch logging (ALL with execution data for Express in dev, ERROR in production if payloads contain personal data) and X-Ray tracing.
  • Alarm on ExecutionsFailed, ExecutionsTimedOut and ExecutionThrottled.

Gotchas

  • The optimized lambda:invoke integration wraps the function result in { "Payload": ..., "StatusCode": ... }. Select Payload or downstream states read the wrapper.
  • Choice states without a Default fail with States.NoChoiceMatched on unexpected input.
  • Mixing JSONPath and JSONata fields in one state is invalid; convert a state completely.
  • The state machine role needs permission for every integration, including states:StartExecution plus EventBridge managed-rule permissions for .sync child executions.
  • Changing a definition does not affect running Standard executions; they finish on the version they started with.

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "AWS Step Functions"
  short_description: "Workflows where every failure has somewhere to go"
  default_prompt: "$aws-step-functions Add retries and a compensation path to this order workflow state machine"
references/data-flow.md94 lines, 4.1 KB
# Step Functions data flow reference

Read this when writing or converting the input/output mapping of states. Pick one query
language per state; JSONata is the better default for new work because one `Output` field
replaces four JSONPath filters and it has real expressions.

## JSONata states

Expressions are strings wrapped in `{% ... %}`. Anything else is a literal.

| Field | Purpose | Replaces (JSONPath) |
|---|---|---|
| `Arguments` | Build the request sent to the task or the input of a Parallel/Map branch | `InputPath` + `Parameters` |
| `Output` | Shape what the state passes on; default is the task result | `ResultSelector` + `ResultPath` + `OutputPath` |
| `Assign` | Store variables for later states | (variables also work in JSONPath) |
| `Condition` | Boolean expression in a Choice rule | `Variable` + comparison operators |
| `Items` | Array for a Map state | `ItemsPath` |

Reserved variable `$states`:

| Expression | Value |
|---|---|
| `$states.input` | The state's raw input |
| `$states.result` | Task, Parallel or Map result (only in `Output` and `Assign`) |
| `$states.errorOutput` | Error and cause (only in `Catch` `Output`/`Assign`) |
| `$states.context` | Context object: `Execution.Id`, `Execution.StartTime`, `State.EnteredTime`, `State.RetryCount`, `Task.Token`, `Map.Item.Value` |

Examples:

```json
"Assign": { "orderId": "{% $states.input.order.id %}", "total": "{% $sum($states.input.order.lines.price) %}" },
"Output": "{% { 'orderId': $orderId, 'chargeId': $states.result.Payload.id } %}"
```

```json
{
  "Type": "Choice",
  "Choices": [
    { "Condition": "{% $total >= 1000 %}", "Next": "ManualReview" },
    { "Condition": "{% $states.input.country in ['US','CA'] %}", "Next": "DomesticShipping" }
  ],
  "Default": "InternationalShipping"
}
```

Useful built-ins beyond standard JSONata: `$partition`, `$range`, `$hash`, `$random`,
`$uuid`, `$parse` (string to JSON). `$now()` and other non-deterministic functions are
evaluated when the state runs.

## JSONPath states

Processing order for a Task: `InputPath` selects part of the input, `Parameters` builds
the request (keys ending in `.$` take a path or intrinsic), the task runs,
`ResultSelector` reshapes the raw result, `ResultPath` places it into the original input,
`OutputPath` filters what is passed on.

```json
{
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": { "FunctionName": "charge-card", "Payload.$": "$.order" },
  "ResultSelector": { "chargeId.$": "$.Payload.id" },
  "ResultPath": "$.charge",
  "OutputPath": "$",
  "Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "Compensate" }],
  "Next": "Ship"
}
```

- `"ResultPath": null` discards the result and passes the input through unchanged.
- `"ResultPath": "$"` (the default) replaces the whole input with the result; the common
  cause of "where did my order data go".
- Context object: `$$.Execution.Id`, `$$.Task.Token`, `$$.Map.Item.Value`.

Intrinsic functions (JSONPath only): `States.Format('order-{}', $.id)`,
`States.StringToJson($.body)`, `States.JsonToString($)`, `States.Array(...)`,
`States.ArrayPartition($.items, 50)`, `States.ArrayLength($.items)`,
`States.MathAdd($.n, 1)`, `States.UUID()`, `States.Hash($.x, 'SHA-256')`,
`States.Base64Encode(...)`.

## Converting JSONPath to JSONata

| JSONPath | JSONata |
|---|---|
| `"Parameters": { "Payload.$": "$.order" }` | `"Arguments": { "Payload": "{% $states.input.order %}" }` |
| `"ResultSelector": { "id.$": "$.Payload.id" }` plus `"ResultPath": "$.charge"` | `"Output": "{% $merge([$states.input, {'charge': {'id': $states.result.Payload.id}}]) %}"` |
| `"ResultPath": null` | `"Output": "{% $states.input %}"` |
| `"Variable": "$.total", "NumericGreaterThanEquals": 1000` | `"Condition": "{% $states.input.total >= 1000 %}"` |
| `"ItemsPath": "$.lines"` | `"Items": "{% $states.input.lines %}"` |
| `States.Format('order-{}', $.id)` | `"{% 'order-' & $states.input.id %}"` |
| `$$.Task.Token` | `$states.context.Task.Token` |

Convert a whole state at a time, then run TestState with `--inspection-level DEBUG` on it
to compare the output with the old version.

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem