Repository navigation
PTC run_code workers exit silently with code 0 on Windows: ELECTRON_RUN_AS_NODE is not preserved when the worker env is rebuilt
#12956
Replies: 1 comment
|
Your diagnosis is right, and 0.2.2 keeps the flag. At The packaged branch still adds Your You can hand this to an AI assistant to compare those lines against your installed |
Uh oh!
There was an error while loading. Please reload this page.
Summary
On Windows packaged builds, PTC
run_codenever executes. The worker process is started withprocess.execPath— which in a packaged Electron build is the Electron binary — but the PTC runtime rebuilds the worker environment from scratch and dropsELECTRON_RUN_AS_NODE, the flag that makes that binary behave as Node.The child therefore boots as a second Electron GUI instance, fails to acquire the single-instance lock, and exits cleanly. The parent reports:
Exit code
0, stderr empty, captured output empty.Environment
@deepseek-ai/dsh-desktop-runtime:0.1.0-rc.22(packaged Electron build)24.17.0(x64) — not the binary used by the PTC worker in this configurationDeepSeek Harness.exe, viaprocess.execPathptcagent preset is installed and mounts withmode: ptcLine numbers below refer to the
0.1.0-rc.22build (lib/*.jsinside the packagedapp.asar).Steps to reproduce
run_codewith any minimal program.Reproduced with four minimal variants —
console.log("x"), returning an object, returning a primitive, and writing directly toprocess.stdout. All four fail identically, with empty captured output.Expected behavior
The worker starts as Node, opens its inherited control channel, executes the submitted program, and returns output.
Actual behavior
The worker starts as the Electron binary without
ELECTRON_RUN_AS_NODE, exits with code0before the bootstrap handshake, and is reported asworker-exit.Root cause
Chain, each step verifiable in the shipped code:
The worker environment is rebuilt.
packages/ptc-runtime/ptc-runtime-node/lib/index.js:956buildsenvby assigningundefinedto every variable not inSTARTUP_ENVIRONMENT_NAMES:That allowlist does not contain the flag.
lib/index.js:478-485lists onlyPATH,PATHEXT,SYSTEMROOT,WINDIR,TEMP,TMP.dsh-subprocess-localthen removes it entirely. In the Windows branch ofchildEnv()the inherited entry with a matching name is dropped first, then theundefinedplaceholder is pushed;targetEnvironment()then filters the placeholder out:Net effect: the variable is neither inherited nor set — it is absent.
The worker executable defaults to
process.execPath.lib/index.js:794:The composed row sets no such config (
packages/.../dsh-base/cordis.patch.yml:369-370— aptc-runtimerow with noconfigblock).In a packaged build that is the Electron binary. The desktop shell resolves its runtime "node" to
process.execPathin packaged builds and must passELECTRON_RUN_AS_NODE: "1"for it to behave as Node:Without the flag the child is a GUI app. It takes the single-instance path, loses the lock, and quits with code
0, never opening the control channel. The parent'sprocessFinishedhandler maps that tokind: "worker-exit"with the message templateNode process exited before completing (${exitCode}).Exit code
0with empty stderr is what rules out the usual suspects: a module-load failure would exit1with stderr; a sandbox denial would classify assandbox-unavailable; a framing problem asprotocol; an overrun asoutput-limit.Suggested minimal fix
Preserve the flag when the worker environment is built — only when the parent already carries it and the worker is the parent's own binary:
Why the two conditions:
!== void 0— never invent the flag; a plain-Node parent (dev checkout, Linuxnode dsh) does not carry it, so nothing is injected.executable === process.execPath— only propagate when the worker is the parent's own binary. An explicitly configurednodeExecutablepointing at a standalonenode.exetherefore never receives it.The value is copied rather than hard-coded to
"1", so the patch mirrors whatever the host set and needs no change if Electron alters the flag's representation.That is the whole patch: one file, one hunk.
Note: no bootstrap change is needed
runNodeMain()already enforces the "starts empty" contract on entry:Its own
STARTUP_ENVIRONMENT_NAMES(lib/process.js:1044-1051) is identical to the parent-side list and does not include the flag. So the flag is deleted before any submitted program runs, andprocess.envis replaced with an empty object — neither the program nor any child it spawns can observe it. No change is suggested tosrc/process.ts.Test suggestions
Unit
nodeExecutableis unset.nodeExecutableis configured, or when the parent lacks it.undefined.runNodeMain()still emptiesprocess.env, so the flag is not observable by the submitted program.Integration (Windows)
windows-jobpath.fallbackpath (force it by stubbing the Win32 Job probe).End-to-end (packaged build)
run_codereturns output and values for the four minimal variants.run_codedoes not inherit the flag.Cross-platform
Not verified
ELECTRON_RUN_AS_NODE— only the binary ships, so this could not be cited from the package. (This is also why the existingrunNodeMaincleanup is safe.)process.versions.electronunder run-as-node mode; the patch deliberately does not depend on it.windows-jobvsfallback) a given machine takes.Additional context
Configuring
nodeExecutablealone does not work around this. WithoutbootstrapPath, the worker bootstrap still resolves toprocess.jsinsideapp.asar, and a standalone Node has no ASAR support — that path is not mapped toapp.asar.unpackedby the filesystem provider, and the package is not unpacked. It trades this failure forCannot find module …app.asar\…\process.js. SonodeExecutableis currently unusable in packaged builds, which is a further argument for fixing this at the environment-construction site.All reactions