-
Notifications
You must be signed in to change notification settings - Fork 10
Description
Problem
In RuntimeOrchestrationContext.resume() (packages/durabletask-js/src/worker/runtime-orchestration-context.ts, lines 148-166), when the previous task has failed and the generator catches the thrown exception, there is no validation that the generator yields a Task object.
If the orchestrator catches an exception and yields a non-Task value:
async function* myOrchestrator(ctx) {
try {
yield ctx.callActivity(someActivity);
} catch {
yield "not a task"; // Bug: should fail with clear error
}
}The code silently falls through, leaving _previousTask pointing to the old failed task. On the next resume() call, the same exception is thrown to the generator in an unexpected location, causing confusing behavior.
Root Cause
The isComplete path (line 184-186) correctly validates the generator output:
if (!(value instanceof Task)) {
throw new Error("The orchestrator generator yielded a non-Task object");
}The isFailed path (lines 159-166) lacks this same validation:
if (throwResult.value instanceof Task) {
this._previousTask = throwResult.value;
if (this._previousTask.isComplete) {
await this.resume();
}
return;
}
// <-- Falls through silently when value is not a TaskProposed Fix
Add the same non-Task validation error after the instanceof Task check in the isFailed path, matching the existing validation in the isComplete path.
Impact
Severity: Medium. When an orchestrator yields a non-Task value after catching an exception from a failed task:
- Instead of a clear "yielded a non-Task object" error, the orchestration enters an inconsistent state
- On the next event, the same exception is re-thrown to the generator in an unexpected location
- The resulting error message is confusing and does not indicate the actual problem (the non-Task yield)
This primarily affects orchestrators that catch task failures and have a programming error in their catch handler.