aws / aws/aws-cdk

(stepfunctions): `AfterwardsOptions.includeOtherwise` not working as described/intended

Open
#37,649 5 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-stepfunctions bug effort/medium p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

When using the `Choice` state, there is a `Choice.afterwards` method to get a chain including the reachable end states to help build composable blocks for a state machine definition.

The `includeOtherwise` prop for that method isn't working as described in the interface where when set to false (by default), a state added from using the `Choice.otherwise` method is still included as an end state (expected to not be included).

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

Documentation for [`AfterwardsOptions.includeOtherwise`](https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/choice.ts#L133) says:
> Whether to include the default/otherwise transition for the current Choice state

I expect that when I have a `Choice` state, call `otherwise` to add a default, and then call `afterwards` to get a chain with the end states, the state added when calling `otherwise` should not be included.

### Current Behavior

A state added when calling `Choice.otherwise` is included as part of the end states in the chain from `Choice.afterwards` even when `includeOtherwise` is false/unspecified. This can be problematic if that state already has a next state.

### Reproduction Steps

Example with looping back to state before choice and adding another step the choice using afterwards
```ts
const waitToRepeat = new Wait(this, "Wait", {
time: WaitTime.duration(cdk.Duration.seconds(60)),
});
const passCheckStatus = new Pass(this, "CheckStatus"); // Would be a Lambda function in practice
const isComplete = new Choice(this, "IsComplete")
.when(Condition.stringEquals("$.status", "FAILED"), new Fail(this, "Failed"))
.when(Condition.stringEquals("$.status", "COMPLETED"), new Pass(this, "Completed"))
.otherwise(waitToRepeat);

const definition = waitToRepeat
.next(passCheckStatus)
.next(isComplete.afterwards())
.next(new Pass(this, "DoOtherThing"));
new StateMachine(this, "MyStateMachine", {
definitionBody: DefinitionBody.fromChainable(definition),
});
```

Results in an error
> «StateAlreadyHasNextState» State 'CheckStatus' already has a next state

### Possible Solution

_No response_

### Additional Information/Context

Call path:
- `Choice.otherwise` -> `State.makeDefault` -> `this.defaultChoice = def`

https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/choice.ts#L75-L76

https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/state.ts#L471-L476

- `Choice.afterwards` -> `State.findReachableEndStates` -> `State.outgoingTransitions` -> `ret.push(this.defaultChoice)`

https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/choice.ts#L85-L86

https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/state.ts#L205-L214

https://github.com/aws/aws-cdk/blob/b349792201b0c5e1f8aade67b4dfcf6945b7edaa/packages/aws-cdk-lib/aws-stepfunctions/lib/states/state.ts#L632-L635

### AWS CDK Library version (aws-cdk-lib)

2.250.0

### AWS CDK CLI version

n/a

### Node.js Version

n/a

### OS

n/a

### Language

TypeScript

### Language Version

_No response_

### Other information

Trying to create something similar to demo workflow [Poll for job status with Lambda and AWS Batch](https://docs.aws.amazon.com/step-functions/latest/dg/sample-project-job-poller.html).

Note the intention is specifically having this polling pattern in a method that returns the chain (to be reusable) where the next step afterwards is unknown.
```ts
private pollStatus(id: string, operation: string): Chain { ... }

// pollStatus(...).next(...).next(pollStatus(...))
```

Current workaround: use `Chain.sequence` and manually specify the desired reachable end states

Contributor guide

Open the contributing guide

Research direction

Start in packages/aws-cdk-lib/aws-stepfunctions/lib/states/choice.ts and follow the referenced State.findReachableEndStates and outgoingTransitions paths in state.ts. Inspect the existing Choice.afterwards tests, reproduce the supplied looping example, and add coverage showing that includeOtherwise=false excludes the default transition while true includes it. Done means the regression is covered and the existing state-machine behavior remains valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.