Repository navigation
open-in-app launches Electron applications as Node.js: ELECTRON_RUN_AS_NODE leaks into the child environment #8970
Replies: 1 comment
|
Independent reproduction on a second Windows machine, with PEB-level evidence for the environment leak and an exact patch target. No new information contradicts the report above — this adds the measurement layer and one extra injection source. Environment
Same symptom: split button → VS Code → generic toast; DevTools shows 1. The leak is in the OS environment block, not just
|
| Process | ELECTRON_RUN_AS_NODE in OS env block |
|---|---|
| Electron main (29332) | absent |
| host server (15560) | 1 |
This is consistent with the report's point that removing the selector from process.env does not remove it from the OS block. Restarting DSH from a clean terminal does not help, because the main process injects it into the server child again.
2. The filter that lets it through, in both loaded copies
@deepseek-ai/dsh-subprocess, src/index.ts (compiled lib/index.js:32,52):
const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i;
...
for (const [key, value] of Object.entries(process.env))
if (value !== void 0 && !SENSITIVE_ENV_PATTERN.test(key) && !key.toUpperCase().startsWith("DSH_"))
env[key] = value;Both copies that are actually resolvable on an installed desktop build have identical logic:
%USERPROFILE%\.dsh\profiles\node_modules\@deepseek-ai\dsh-subprocess\lib\index.js:52resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-subprocess\lib\index.js
and dsh-host-open-in-app imports it directly (lib/index.js:8, used at lib/index.js:354 inside launchDetachedApp). So ELECTRON_RUN_AS_NODE survives the scrub.
3. Deterministic, single-variable reproduction
Faithful replay of resolve → scrubbedParentEnv() → launchDetachedApp (same 1000 ms watch window), on E:\Microsoft VS Code\Code.exe with a real existing workspace directory. The only difference between A and B is one key:
A) env = scrubbed(server env) -> outcome = failed, exit = 1 -> HTTP 502
B) env = scrubbed(server env) minus the one key -> outcome = launched -> HTTP 200
Repeated three times each:
A) 3/3 status=1, stderr first line: node:internal/modules/cjs/loader:1575
B) 3/3 status=0, no stderr
Full stderr of the failing spawn (proving it is Electron-as-Node, not a launcher problem):
node:internal/modules/cjs/loader:1575
throw err;
Error: Cannot find module '<the workspace directory>'
at Module._resolveFilename (node:internal/modules/cjs/loader:1571:15)
...
The route handler's answer matches: POST /open-in-app/open replies 502 only for launch-failed, which requires an absolute path naming an existing directory and an already-resolved catalog app — both true here, so the failure is confined to the spawn/exit step.
4. One additional injection source
Besides resources\runtime\cli\bin\dsh.cmd (which the report already cites), this build also ships:
rem resources\runtime\bin\node.cmd
set ELECTRON_RUN_AS_NODE=1
"%DSH_DESKTOP_NODE_EXECUTABLE%" --expose-internals %*Note node.cmd has no setlocal, so ELECTRON_RUN_AS_NODE=1 persists in the caller's shell after it runs. (The report's other point stands: there are two distinct mechanisms — the deliberate design that keeps the variable for child startup, and a launching shell that was already carrying it.) Worth mentioning only because it widens the set of ways a user's GUI process can end up contaminated; the fix below is unaffected.
5. Scope check
Enumerating every App Paths entry under both HKCU and HKLM that points at an existing file: 41 entries, of which exactly one is an Electron binary (VS Code). Consistent with the report's conclusion that only Electron GUI launchers are affected, and that shell-open entries (File Explorer) are not — explorer.exe returning exit 1 is accepted by runExplorer as a delegated handoff, so the file-manager button succeeds.
Fix
The minimal, behaviour-preserving change is in the shared scrub, so every catalog entry benefits and no launch path can forget it:
// @deepseek-ai/dsh-subprocess — scrubbedParentEnv()
if (key.toUpperCase() === "ELECTRON_RUN_AS_NODE") continue;Agreed with the report that the same reasoning extends to the other ELECTRON_* / Node-mode selectors (NODE_OPTIONS in particular). Filtering by intent ("a variable that changes what the child process is") rather than by a name list would also close the class, and would let the deliberate child-startup use keep working since that value is supplied explicitly by the caller rather than inherited.
Happy to test a build if one becomes available.
Uh oh!
There was an error while loading. Please reload this page.
open-in-app launches Electron applications as Node.js:
ELECTRON_RUN_AS_NODEleaks into the child environmentSummary
"Open In…" → VS Code always fails with the client toast
"Could not open. Try again." (
open-in-appdictionary,path.openError, shown wheneverPOST /open-in-app/openanswers non-OK).The launcher is resolved correctly — the host finds a verified
Code.exeand the request passesvalidation. The failure is in the launch: the child inherits
ELECTRON_RUN_AS_NODE=1, so VS Code'sElectron binary starts as a plain Node.js interpreter instead of the editor, treats the
workspace path as a script to execute, and exits 1 immediately.
Environment
nightly;@deepseek-ai/dsh-*0.2.0-rc.2%LOCALAPPDATA%\Programs\Microsoft VS Code\Code.exe)Steps to reproduce
Observed: toast "Could not open. Try again.", no editor window opens.
Direct reproduction without the UI (any shell on this host):
Control: with
ELECTRON_RUN_AS_NODEremoved from the child environment, the same invocation startsthe editor and opens the requested directory (the process stays alive and writes normal main-process
lines such as
[main …] update#setState disabled).Root cause and evidence
$env:ELECTRON_RUN_AS_NODEis1in the host process environment, while[Environment]::GetEnvironmentVariable('ELECTRON_RUN_AS_NODE','User')and…,'Machine'are bothempty. The variable therefore comes from DSH, not from the user's profile.
The desktop CLI shim sets it explicitly before running the desktop executable as Node:
The shipped documentation describes this as intentional but scoped:
"The host preserves
ELECTRON_RUN_AS_NODEonly for child startup so the Desktop executable runsthe Node bootstrap; the bootstrap removes the selector before evaluating model code."
That removal affects the JS
process.envdictionary; the OS environment block keeps thevariable, so native grandchildren — including every application launched by
open-in-app— inheritit.
The agent startup path already excludes it explicitly (its environment builder filters out
ELECTRON_RUN_AS_NODEtogether with the other startup names). Theopen-in-applaunch path doesnot, so the fix pattern already exists in the codebase.
Scope: every catalog entry that launches an Electron GUI binary directly is affected — VS Code,
VS Code Insiders, Cursor, Windsurf. The three entries that work on this host do not launch an
Electron GUI binary in that shape (File Explorer and Git Bash are native; GitHub Desktop is invoked
through its packaged
cli.js).Expected behaviour
Where the host launches an application for the user, the child environment must not carry DSH's own
Electron startup selectors. The launched application should start as itself.
Suggested fix
Strip
ELECTRON_RUN_AS_NODE— and reasonably the rest of theELECTRON_*startup selectors — fromthe environment handed to a catalog application, mirroring the exclusion already applied to the agent
startup environment. The scrub belongs in the
open-in-applaunch path (host packagepackages/host/open-in-app) or in the shared launch helper it uses, so that every catalog entrybenefits rather than VS Code alone.
Related reports (according to header)
path.openError.Workaround for users
Launch the editor from the Start menu or File Explorer: that environment does not carry the variable,
and the editor works normally.
All reactions