Problem
RuntimeOrchestrationContext.run() does not validate that the first value yielded by an orchestrator generator is a Task instance. The resume() method (which processes subsequent yields) already validates this with an instanceof Task check and throws a clear error:
// In resume() — line 184
if (!(value instanceof Task)) {
throw new Error("The orchestrator generator yielded a non-Task object");
}
However, run() (which processes the first yield) silently accepts any value — null, undefined, plain objects, or primitives — and assigns it to _previousTask without validation. There was even a TODO comment acknowledging this gap: "// TODO: check if the task is null?".
File: packages/durabletask-js/src/worker/runtime-orchestration-context.ts, lines 109–132
Root Cause
The run() method was written without the same validation guard that resume() has. When the generator yields a non-Task value on its first yield:
_previousTask is set to the non-Task value (e.g., null, 42, {foo: "bar"})
- The
instanceof Task check on the isComplete guard fails silently — no error, just skips
- Later, when
resume() is called by a completion event, it checks _previousTask.isFailed and _previousTask.isComplete — both are undefined on non-Task values
- The generator is never advanced, and the orchestration hangs indefinitely with no error
Impact
- Severity: Medium — causes orchestrations to hang silently with no error message
- Affected scenarios: Any orchestrator that accidentally yields a non-Task value on its first yield (e.g., a raw Promise,
null, or forgetting to call a context method that returns a Task)
- Debugging difficulty: High — no error is thrown, no log is emitted; the orchestration simply stops progressing
Problem
RuntimeOrchestrationContext.run()does not validate that the first value yielded by an orchestrator generator is aTaskinstance. Theresume()method (which processes subsequent yields) already validates this with aninstanceof Taskcheck and throws a clear error:However,
run()(which processes the first yield) silently accepts any value —null,undefined, plain objects, or primitives — and assigns it to_previousTaskwithout validation. There was even a TODO comment acknowledging this gap:"// TODO: check if the task is null?".File:
packages/durabletask-js/src/worker/runtime-orchestration-context.ts, lines 109–132Root Cause
The
run()method was written without the same validation guard thatresume()has. When the generator yields a non-Task value on its first yield:_previousTaskis set to the non-Task value (e.g.,null,42,{foo: "bar"})instanceof Taskcheck on theisCompleteguard fails silently — no error, just skipsresume()is called by a completion event, it checks_previousTask.isFailedand_previousTask.isComplete— both areundefinedon non-Task valuesImpact
null, or forgetting to call a context method that returns a Task)