[GH-ISSUE #3535] [Bug] Electron reached through esbuild's __require shim is invisible (0 windows / 0 IPC on esbuild-bundled main processes) #1310

Closed
opened 2026-10-16 21:04:05 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @Rinkia on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3535

Before submitting

  • I searched the open and closed issues for a related report. This is the esbuild sibling of #2932 (webpack module registry); its open fix #2948 covers webpack bootstrap externals only. Also related: #1802 (namespace form) and #1656 (lexical aliases), both fixed.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

esbuild bundles of CommonJS code for Node/Electron replace require with its __require shim, which minifiers then rename. analyze_javascript_application doesn't follow Electron through that shim. The same program reports one window, two webPreferences and one IPC handler with require("electron"), and all zeros with the shim:

Fixture browser_windows explicit_web_preferences ipc.operations ipc.main_handlers
f_plainrequire.js: var cy=require("electron") 1 2 1 1
e_esbuild.js: var cy=Q("electron") via esbuild's shim 0 0 0 0

The result carries no unknown pointing at the unresolved loader call, only the generic limitations, so an agent reads "no windows, no IPC".

Reproduction

Minimal fixture (the shim is copied from esbuild's minified output, as found in a real app):

// e_esbuild.js
"use strict";(()=>{var Q=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});
var cy=Q("electron");
var w=new cy.BrowserWindow({webPreferences:{nodeIntegration:!0,contextIsolation:!1}});
cy.ipcMain.handle("get-version",()=>"1.0.0");
})();

f_plainrequire.js is identical except var cy=require("electron").

rea analyze-javascript-application ./fx --source-path e_esbuild.js --json
rea analyze-javascript-application ./fx --source-path f_plainrequire.js --json

(fx/package.json: {"name":"fx","version":"1.0.0","main":"a_literal.js"}.)

Real-world case: Joplin desktop 3.7.21 (open source, AGPL; Joplin-3.7.21.deb from the GitHub release, sha256 14de2d5524e45341de5f0b68a1e78191b796a6c9817949c49b80afe5cd6ab965, matching the release digest):

  • app.asar/package.json declares "main": "main.bundle.js" (13 MB esbuild bundle). The bundle defines Q=(e=>typeof require<"u"?require:…)(function(e){…}) and calls Q("electron") 45 times, require("electron") 0 times.
  • rea analyze-javascript-application resources --source-path app.asar/main.bundle.js --max-heap-mb 4096 reports every Electron field as 0 (browser_windows: 0, ipc.operations: 0, …).
  • Running on the unbundled ElectronAppWrapper.js that also ships in the ASAR finds the windows. But that file isn't what Electron executes; the bundle is.
  • Caveat on the real case: the bundle's semantic graph also exceeds the parent result budget ("static and module facts were retained"), so Joplin alone doesn't isolate the cause. The minimal fixture does: no budget pressure, and only the loader spelling differs.

Expected behavior

  • Treat esbuild's __require shim (X=(e=>typeof require<"u"?require:…)(…)) as an alias of require, so X("electron") carries Electron identity like require("electron"), as #2948 does for webpack's verified bootstrap.
  • At minimum, when a call resolves to an unrecognised loader for "electron", report an explicit unknown rather than silent zeros.

Impact and scope

  • Affects any Electron app whose main process is bundled with esbuild for a Node/Electron target (esbuild emits __require for CommonJS require calls in bundled code). In those apps the Electron boundary summary is empty.
  • Silent: the summary looks complete.
  • Deterministic.

REA version

rea-agents@6.5.0

Runtime and platform

Linux x64 Docker container (Ubuntu 24.04) on Windows 11 / Docker Desktop, Node 24.21.0.

Target and artifact context

Synthetic fixture above (no external dependency). Real-world confirmation on the public Joplin 3.7.21 release.

Interface

CLI (MCP shares the analysis path; not separately run).

Originally created by @Rinkia on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3535 ### Before submitting - [x] I searched the open and closed issues for a related report. This is the esbuild sibling of #2932 (webpack module registry); its open fix #2948 covers webpack bootstrap externals only. Also related: #1802 (namespace form) and #1656 (lexical aliases), both fixed. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior esbuild bundles of CommonJS code for Node/Electron replace `require` with its `__require` shim, which minifiers then rename. `analyze_javascript_application` doesn't follow Electron through that shim. The same program reports one window, two webPreferences and one IPC handler with `require("electron")`, and all zeros with the shim: | Fixture | `browser_windows` | `explicit_web_preferences` | `ipc.operations` | `ipc.main_handlers` | |---|---|---|---|---| | `f_plainrequire.js`: `var cy=require("electron")` | 1 | 2 | 1 | 1 | | `e_esbuild.js`: `var cy=Q("electron")` via esbuild's shim | **0** | **0** | **0** | **0** | The result carries no unknown pointing at the unresolved loader call, only the generic limitations, so an agent reads "no windows, no IPC". ### Reproduction Minimal fixture (the shim is copied from esbuild's minified output, as found in a real app): ```js // e_esbuild.js "use strict";(()=>{var Q=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')}); var cy=Q("electron"); var w=new cy.BrowserWindow({webPreferences:{nodeIntegration:!0,contextIsolation:!1}}); cy.ipcMain.handle("get-version",()=>"1.0.0"); })(); ``` `f_plainrequire.js` is identical except `var cy=require("electron")`. ```bash rea analyze-javascript-application ./fx --source-path e_esbuild.js --json rea analyze-javascript-application ./fx --source-path f_plainrequire.js --json ``` (`fx/package.json`: `{"name":"fx","version":"1.0.0","main":"a_literal.js"}`.) **Real-world case: Joplin desktop 3.7.21** (open source, AGPL; `Joplin-3.7.21.deb` from the GitHub release, sha256 `14de2d5524e45341de5f0b68a1e78191b796a6c9817949c49b80afe5cd6ab965`, matching the release digest): - `app.asar/package.json` declares `"main": "main.bundle.js"` (13 MB esbuild bundle). The bundle defines `Q=(e=>typeof require<"u"?require:…)(function(e){…})` and calls `Q("electron")` 45 times, `require("electron")` 0 times. - `rea analyze-javascript-application resources --source-path app.asar/main.bundle.js --max-heap-mb 4096` reports every Electron field as 0 (`browser_windows: 0`, `ipc.operations: 0`, …). - Running on the unbundled `ElectronAppWrapper.js` that also ships in the ASAR finds the windows. But that file isn't what Electron executes; the bundle is. - Caveat on the real case: the bundle's semantic graph also exceeds the parent result budget ("static and module facts were retained"), so Joplin alone doesn't isolate the cause. The minimal fixture does: no budget pressure, and only the loader spelling differs. ### Expected behavior - Treat esbuild's `__require` shim (`X=(e=>typeof require<"u"?require:…)(…)`) as an alias of `require`, so `X("electron")` carries Electron identity like `require("electron")`, as #2948 does for webpack's verified bootstrap. - At minimum, when a call resolves to an unrecognised loader for `"electron"`, report an explicit unknown rather than silent zeros. ### Impact and scope - Affects any Electron app whose main process is bundled with esbuild for a Node/Electron target (esbuild emits `__require` for CommonJS `require` calls in bundled code). In those apps the Electron boundary summary is empty. - Silent: the summary looks complete. - Deterministic. ### REA version `rea-agents@6.5.0` ### Runtime and platform Linux x64 Docker container (Ubuntu 24.04) on Windows 11 / Docker Desktop, Node 24.21.0. ### Target and artifact context Synthetic fixture above (no external dependency). Real-world confirmation on the public Joplin 3.7.21 release. ### Interface CLI (MCP shares the analysis path; not separately run).
Author
Owner

@Rinkia commented on GitHub (Oct 18, 2026):

Still reproduces on rea-agents@6.7.0. e_esbuild.js gives browser_windows: 0, explicit_web_preferences: 0, ipc operations: 0. The control f_plainrequire.js (the same code with a plain require("electron")) gives 1 / 2 / 1 operation, 1 main handler. The var Q = require; Q("electron") alias fixture (c_alias.js) also gives 0 windows.

<!-- gh-comment-id:6249959891 --> @Rinkia commented on GitHub (Oct 18, 2026): Still reproduces on `rea-agents@6.7.0`. `e_esbuild.js` gives `browser_windows: 0, explicit_web_preferences: 0, ipc operations: 0`. The control `f_plainrequire.js` (the same code with a plain `require("electron")`) gives `1 / 2 / 1 operation, 1 main handler`. The `var Q = require; Q("electron")` alias fixture (`c_alias.js`) also gives 0 windows.
Author
Owner

@Y0UW1SH commented on GitHub (Oct 19, 2026):

Thanks for rechecking on 6.7.0. We're addressing both the esbuild shim and the plain var Q = require; Q("electron") alias case in #4922, which is still open and has not been merged or released.

The fix tracks the underlying CommonJS loader through aliases and esbuild's guarded helper, including minified helper names, then carries Electron identity into BrowserWindow, webPreferences and IPC analysis. It checks binding changes, loader replacement, helper mutations and captured values so a function merely named Q or __require does not automatically count as the real loader. The bundled regression targets the same result as your control: 1 window, 2 explicit webPreferences, 1 IPC operation and 1 main handler.

At the current PR head (3deab802), 6,281 JavaScript tests and 300 isolated Node comparisons pass, and 60 shared fixture sources match through the production parser/analyzer. We're still hardening edge cases documented in the PR. Full CLI/MCP verification is currently blocked by a process-ownership inspection failure on our test host, and we have not rerun the Joplin bundle, so we're not claiming end-to-end or real-application validation yet.

Replying to https://github.com/morluto/rea/issues/3535#issuecomment-6249959891.

<!-- gh-comment-id:6272418551 --> @Y0UW1SH commented on GitHub (Oct 19, 2026): Thanks for rechecking on 6.7.0. We're addressing both the esbuild shim and the plain `var Q = require; Q("electron")` alias case in #4922, which is still open and has not been merged or released. The fix tracks the underlying CommonJS loader through aliases and esbuild's guarded helper, including minified helper names, then carries Electron identity into BrowserWindow, webPreferences and IPC analysis. It checks binding changes, loader replacement, helper mutations and captured values so a function merely named `Q` or `__require` does not automatically count as the real loader. The bundled regression targets the same result as your control: **1 window, 2 explicit webPreferences, 1 IPC operation and 1 main handler**. At the current PR head (`3deab802`), 6,281 JavaScript tests and 300 isolated Node comparisons pass, and 60 shared fixture sources match through the production parser/analyzer. We're still hardening edge cases documented in the PR. Full CLI/MCP verification is currently blocked by a process-ownership inspection failure on our test host, and we have not rerun the Joplin bundle, so we're not claiming end-to-end or real-application validation yet. Replying to https://github.com/morluto/rea/issues/3535#issuecomment-6249959891.
Author
Owner

@CassieFox23 commented on GitHub (Oct 21, 2026):

Thanks, Rinkia, for the Joplin case and the plain-require control. From reading main at 01788bac8, not a run: still valid. Nothing under src handles esbuild's __require shim. The inspectEsbuildWrapper (src/domain/javascript/javascriptStaticAnalysisBundler.ts:157-181) recovers esbuild module tables only. The shim's Dynamic require of text appears nowhere in src. Electron identity still starts from a plain or aliased require("electron"). PR #4922 closed on 2026-10-20 without merging. Likely area: JavaScript static analysis, Electron identity.

🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6321733612 --> @CassieFox23 commented on GitHub (Oct 21, 2026): Thanks, Rinkia, for the Joplin case and the plain-require control. From reading main at 01788bac8, not a run: still valid. Nothing under src handles esbuild's `__require` shim. The `inspectEsbuildWrapper` (`src/domain/javascript/javascriptStaticAnalysisBundler.ts:157-181`) recovers esbuild module tables only. The shim's `Dynamic require of` text appears nowhere in src. Electron identity still starts from a plain or aliased `require("electron")`. PR #4922 closed on 2026-10-20 without merging. Likely area: `JavaScript static analysis, Electron identity`. 🤖 Written with Claude Code (Fable 5.1, verified by Opus 5.5), posted with my OK 🦊
Author
Owner

@Rinkia commented on GitHub (Oct 26, 2026):

Re-tested on rea-agents@6.8.0 (Docker, Node 24.22.1) with the reproduction fixtures from this issue. Thanks for 36f528efc.

Fixture 6.7.0 6.8.0
e_esbuild.js (esbuild's __require shim: typeof require<"u"?require:typeof Proxy<"u"?new Proxy(…):…) 0 windows, 0 webPrefs, 0 IPC unchanged: 0 / 0 / 0
c_alias.js (var Q = require; Q("electron")) 0 windows unchanged
f_plainrequire.js (control) 1 / 2 / 1 operation, 1 main handler unchanged

What changed: the esbuild result now has two unresolved-reference records for require.apply(this,arguments) that 6.7.0 didn't have. So the shim's loader call is visible as unresolved rather than missing. Electron identity, windows and IPC are still not recovered, and the result's Electron summary still reads zero without naming the shim.

That matches the commit's docs ("This does not interpret typeof or Proxy behavior in bundler shims"). If the intended scope was explicit uncertainty rather than recovering Electron through the canonical esbuild shim, that's fine. But the reproduction here (and Joplin 3.7.21's main.bundle.js) still reports no windows or IPC, so a follow-up for the shim itself may be worth tracking.

<!-- gh-comment-id:6412664943 --> @Rinkia commented on GitHub (Oct 26, 2026): Re-tested on `rea-agents@6.8.0` (Docker, Node 24.22.1) with the reproduction fixtures from this issue. Thanks for 36f528efc. | Fixture | 6.7.0 | 6.8.0 | |---|---|---| | `e_esbuild.js` (esbuild's `__require` shim: `typeof require<"u"?require:typeof Proxy<"u"?new Proxy(…):…`) | 0 windows, 0 webPrefs, 0 IPC | **unchanged**: 0 / 0 / 0 | | `c_alias.js` (`var Q = require; Q("electron")`) | 0 windows | unchanged | | `f_plainrequire.js` (control) | 1 / 2 / 1 operation, 1 main handler | unchanged | What changed: the esbuild result now has two `unresolved-reference` records for `require.apply(this,arguments)` that 6.7.0 didn't have. So the shim's loader call is visible as unresolved rather than missing. Electron identity, windows and IPC are still not recovered, and the result's Electron summary still reads zero without naming the shim. That matches the commit's docs ("This does not interpret `typeof` or Proxy behavior in bundler shims"). If the intended scope was explicit uncertainty rather than recovering Electron through the canonical esbuild shim, that's fine. But the reproduction here (and Joplin 3.7.21's `main.bundle.js`) still reports no windows or IPC, so a follow-up for the shim itself may be worth tracking.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1310
No description provided.