[Bug]: Nested function returns make evaluate()/js() silently return null
Affected component
JavaScript string evaluation in the open helper runtime: page.evaluate() on main/dev, and the legacy js() helper in the current app/beta runtime.
What happened?
A valid expression containing a return inside a nested function silently loses its result:
// main/dev facade:
await page.evaluate('[1, 2].map(x => { return x * 2; })')
// Expected: [2, 4]
// Actual: null
// Installed app's legacy helper:
await js('[1, 2].map(x => { return x * 2; })')
// Also returns null.This affects common page extraction expressions such as .map(el => { return el.textContent; }). Function declarations and methods can trigger the same failure. A control, return 42, correctly returns 42.
Root cause
src/cdp-eval.ts scans for the return word without tracking function scope. evaluate()/js() then wraps the entire expression in an IIFE with no outer return:
(function () {
[1, 2].map(x => { return x * 2; })
})()The result is undefined, which runtimeValue() converts to null. Conversely, the current startsWith('(') exception misses a real outer return in (1 + 2); return 4; and sends an illegal top-level return to the browser.
Related: #218 fixes regex-literal handling in this scanner, but does not distinguish nested function returns from outer returns. The scope bug reported here remains after that proposed change.
Reproduction and verification boundary
main:5ca3c36cba2240b8df2e22ba32127747029039d5.dev(patch base):ca746891e208d3a8ca8a20f4ca91eed4ac53c64f.- The same scanner and wrapping condition remain in
2.0.0-beta-devat8ac6e585ee32a5d463368f0ca0ccf3958c165945; that branch exposes this path asjs(). - Reproduced in the installed ego lite app on macOS 26.5.2, using an isolated data-URL test page and the standard
ego-browser nodejsentry point. Both the.map()expression above andfunction f() { return 3; } f()returnednull. - Loaded the patched checkout through
ego-browser nodejs --sdk-path <absolute-bundle-path>in the same task space. The nested callback, function declaration, regex literal, parenthesized prefix plus outer return, and plain outer return all produced their expected values.
Fix available
Commit 57be7da / downloadable patch, based on dev.
- Reuse the existing Acorn dependency to parse the source.
- Detect outer
ReturnStatementnodes while skipping nested functions. - Remove the parenthesis shortcut; real IIFEs already have only nested returns.
- Leave syntax the parser cannot accept unchanged for the browser to report.
- Add regression tests that execute the emitted JavaScript in Node's VM, rather than making a CDP mock return the expected value unconditionally.
Validation: the new regression coverage produced nine failing checks before the fix; all 43 evaluation tests and the full 322-test suite pass after it. Typecheck, changed-file Prettier, git diff --check, and site-skill validation pass. Five focused real-Ego checks pass with the patched bundle. The full browser E2E suite was not run.
The public helper signatures and dependencies are unchanged. Upstream currently allows only collaborators to create pull requests, so the tested fix is supplied as a fork commit for review/application.
Source: citrolabs/ego-lite