# Idle Step Function -- No Executions

> Step Functions bills $0.025 per 1,000 state transitions and nothing while idle, so a state machine with zero ExecutionsStarted over 30 days already costs $0. ZopNight retired this rule in place, with Evaluate returning nil on every input, rather than ship a $0 delete card under a cost category.

Source: https://zop.dev/integrations/aws/recommendations/idle-step-function-no-executions
Updated: 2026-08-19

---

## Per-transition pricing leaves nothing to reclaim

A Step Functions state machine is billed on movement: roughly [$0.025 per 1,000 state
transitions](https://aws.amazon.com/step-functions/pricing/), metered as executions run. A definition that sits there with zero
`ExecutionsStarted` triggers zero transitions and lands on the bill at exactly $0. There is
no rack rate to stop paying, no provisioned capacity to shrink, no off-hours window to
schedule, and no commitment to re-tier. Every axis a cost recommendation could quantify
comes back empty. Deleting an already-free state machine saves nothing, so under ZopNight's
concrete-or-abstain contract for cost findings, the only honest output is no output.

## From a $0 advisory card to an honest abstain

The previous implementation knew all of this and emitted anyway: a SavingsUSD 0 finding filed
under `advisory`, a category the low-savings filter does not treat as cost-driven. That
routing existed purely so the $0 card would survive the filter that exists to drop $0 cards.
The retirement removed the mask: the category is now `idle`, the honest cost-driven label,
and Evaluate returns nil unconditionally. The rule is retired in place, kept in the registry
for identity stability but incapable of producing a recommendation.

## Where ExecutionsStarted still flows

The rule still declares `ExecutionsStarted` ([one of the execution counters Step Functions publishes](https://docs.aws.amazon.com/step-functions/latest/dg/procedure-cw-metrics.html), dimensioned by the state
machine's ARN over a 30-day window) as a required metric, so the aggregator keeps harvesting it.
Two plumbing fixes ride along: the resource type key is `step-function`, matching what the
discoverer actually emits (the old `stepfunctions` literal matched nothing, leaving the
rule's type-join dead), and the metric lands on live resources as a result.

## Counting executions yourself

```bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/States --metric-name ExecutionsStarted \
  --dimensions Name=StateMachineArn,Value=arn:aws:states:us-east-1:111122223333:stateMachine:my-machine \
  --start-time "$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 86400 --statistics Sum
```

All-zero sums for a month means nothing started an execution.

## Retiring an abandoned state machine

An unused state machine is free but not harmless: its IAM role keeps whatever permissions the
workflow once needed, and EventBridge rules or API Gateway integrations may still point at
it. If the workflow is genuinely dead, remove the triggers first, then the state machine,
then the role, in that order, so nothing fires mid-cleanup.
