stepfunctions: Default Choice state not created when using afterwards with includeOtherwise
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the bug
It is not possible to use nested Choice states that only define single `.when()` cases each and utilise `.afterwards({ includeOtherwise: true })` for the Default flow, because the nested Default branch is not created.
The state machine would approximate the following algorithm:
```
if (firstFlag) {
// Do Something
if (secondFlag) {
// Failure
}
}
// All Successful
```
### Expected Behavior
The inner `Choice` should have a `Default` transition to `All Successful`:
```json
{
"StartAt": "First Choice",
"States": {
"First Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.firstFlag",
"BooleanEquals": false,
"Next": "Do Something"
}
],
"Default": "All Successful"
},
"All Successful": {
"Type": "Succeed"
},
"Do Something": {
"Type": "Pass",
"Next": "Second Choice"
},
"Second Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.secondFlag",
"BooleanEquals": false,
"Next": "Failure"
}
],
"Default": "All Successful"
},
"Failure": {
"Type": "Fail"
}
}
}
```

### Current Behavior
The inner `Choice` does not have a `Default` transition created:
```json
{
"StartAt": "First Choice",
"States": {
"First Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.firstFlag",
"BooleanEquals": false,
"Next": "Do Something"
}
],
"Default": "All Successful"
},
"All Successful": {
"Type": "Succeed"
},
"Do Something": {
"Type": "Pass",
"Next": "Second Choice"
},
"Second Choice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.secondFlag",
"BooleanEquals": false,
"Next": "Failure"
}
]
},
"Failure": {
"Type": "Fail"
}
}
}
```

### Reproduction Steps
Create the step machine using the following CDK:
```typescript
new sfn.Choice(stack, "First Choice")
.when(
sfn.Condition.booleanEquals("$.firstFlag", false),
new sfn.Pass(stack, "Do Something").next(
new sfn.Choice(stack, "Second Choice")
.when(sfn.Condition.booleanEquals("$.secondFlag", false), new sfn.Fail(stack, "Failure"))
.afterwards({ includeOtherwise: true })
)
)
.afterwards({ includeOtherwise: true })
.next(new sfn.Succeed(stack, "All Successful"))
```
Execute the step machine using the following input to trigger the missing Default branch:
```json
{
"firstFlag": false,
"secondFlag": true
}
```
### Possible Solution
_No response_
### Additional Information/Context
This could be worked around by providing a dummy `Pass` state after `.afterwards()` definition:
```typescript
new sfn.Choice(stack, "First Choice")
.when(
sfn.Condition.booleanEquals("$.firstFlag", false),
new sfn.Pass(stack, "Do Something").next(
new sfn.Choice(stack, "Second Choice")
.when(sfn.Condition.booleanEquals("$.secondFlag", false), new sfn.Fail(stack, "Failure"))
.afterwards({ includeOtherwise: true })
.next(new sfn.Pass(stack, "Unnecessary Do-Nothing Pass for Default"))
)
)
.afterwards({ includeOtherwise: true })
.next(new sfn.Succeed(stack, "All Successful"))
```

### CDK CLI Version
1.159.0
### Framework Version
_No response_
### Node.js Version
16.13.2
### OS
MacOS
### Language
Typescript
### Language Version
4.5.5
### Other information
Might be related to issue aws/aws-cdk#16210 and discussion aws/aws-cdk#19718.
Contributor guide
Research direction
Start with the TypeScript reproduction in the issue and inspect the generated AWS Step Functions state machine for nested Choice states using afterwards({ includeOtherwise: true }). Compare the inner Choice with the expected JSON and the dummy-Pass workaround. Done means the inner Choice receives a Default transition to All Successful without requiring an unnecessary Pass state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100