Summary
for await (const x of gen) where gen is a variable (or the result of a method call) holding an async generator iterates undefined forever instead of the yielded values. Only a direct call to a named async generator function (for await (x of g())) is lowered correctly.
Repro
async function* g() { yield "a"; yield "b"; }
async function main() {
const gen = g();
for await (const x of gen) console.log(x); // Node: a, b — Perry: undefined ×∞
}
main();
Root cause
The async generator object has a working .next() returning Promise<{ value, done }>, but no [Symbol.asyncIterator], and the for-await lowering only special-cases direct calls to names in async_generator_func_names. Any other shape — a variable, or an async-generator method result like obj.run() — falls through to the ForOfToArray desugar, which reads .length (undefined) on the generator object.
This blocks @openai/codex-sdk natively: its runStreamedInternal does for await (const item of generator) where generator = this._exec.run({...}) (an async-generator method result).
Notes / direction
- A compile-time fix must handle the method-call case (
this._exec.run()), not just bare functions — needs async-generator return-type tracking through variables and method calls.
- A general runtime fix (route the
for await fallback through an async-iterator protocol) must preserve current correct behaviour for for await over arrays and arrays-of-promises (verified working today), so it needs care to avoid regressions.
- Installing
[Symbol.asyncIterator] on async-generator objects is a correct, isolated sub-step but doesn't fix the loop on its own.
Summary
for await (const x of gen)wheregenis a variable (or the result of a method call) holding an async generator iteratesundefinedforever instead of the yielded values. Only a direct call to a named async generator function (for await (x of g())) is lowered correctly.Repro
Root cause
The async generator object has a working
.next()returningPromise<{ value, done }>, but no[Symbol.asyncIterator], and the for-await lowering only special-cases direct calls to names inasync_generator_func_names. Any other shape — a variable, or an async-generator method result likeobj.run()— falls through to theForOfToArraydesugar, which reads.length(undefined) on the generator object.This blocks
@openai/codex-sdknatively: itsrunStreamedInternaldoesfor await (const item of generator)wheregenerator = this._exec.run({...})(an async-generator method result).Notes / direction
this._exec.run()), not just bare functions — needs async-generator return-type tracking through variables and method calls.for awaitfallback through an async-iterator protocol) must preserve current correct behaviour forfor awaitover arrays and arrays-of-promises (verified working today), so it needs care to avoid regressions.[Symbol.asyncIterator]on async-generator objects is a correct, isolated sub-step but doesn't fix the loop on its own.