(stepfunctions): `AfterwardsOptions.includeOtherwise` not working as described/intended
- 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
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