Repository navigation
Replies: 2 comments 1 reply
|
The patch shape is correct: Node’s |
|
Thanks. The shape you describe is the one the patch already takes, and all three points were in it when the review landed (the Patch section lists them). The signal comes off Your regression is already there: a On the oversized read: that is where the crash came from, and the guard shipped on 2026-10-09 in the plugin that hit the 2.6 GB model file. Fifty-three direct One thing I did not re-run: the 22-test figure in the patch evidence, which was measured in the fork clone. What I did check is that the patch still applies cleanly to upstream |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
A Host killed by a signal is reported as a clean stop, and its last stderr line as the cause
Component:
apps/desktop, Host exit reporting insrc/host-process.tsVersion:
0.2.0-rc.2· Platform: macOS arm64, Desktop app (bundled Node24.18.1, Electron44.0.0)Summary
The shell reports a killed Host the same way it reports one that stopped.
closecarries theterminating signal, the handler takes only the exit code, and a signal death arrives as
code === null, so it lands on the same branch as a clean exit:Two Host deaths on 2026-10-09 arrived like this:
The child's last stderr output is appended after a colon, so it reads as the reason for the stop. It
wasn't. Both processes were killed by
SIGTRAP, and the signal survives only in the macOS reportwritten beside the diagnostics file: process
DeepSeek Harness(the Host runs as the app binary withELECTRON_RUN_AS_NODE=1), exceptionEXC_BREAKPOINT, termination reasonTrace/BPT trap: 5,faulting thread
com.apple.main-thread. Nothing in the DSH diagnostic says so, and the dialog can't,because the message above is all it has.
Node emits a
DEP0180warning at most once per process, so it sits in the retained 64 KiB of stderruntil the child writes that much more. In both incidents nothing was written after it, which is why
it read as the cause; the failure itself printed nothing. That warning sent me looking for
fs.Statsconstructors.
What the Host was doing
Both deaths landed about 3 s into the same tool call: a plugin's directory scan that read files
through
node:fsand checked their size afterwards. The scan reached a 2.6 GB model file, and on theruntime the Desktop ships that read does not return an error:
readFileSync(path, 'utf8')on the shipped runtimev22.22.0ERR_STRING_TOO_LONGERR_STRING_TOO_LONGERR_STRING_TOO_LONGERR_STRING_TOO_LONGERR_STRING_TOO_LONGERR_STRING_TOO_LONGERR_STRING_TOO_LONGEvery row was measured on 2026-10-09 against all-zero sparse files, the app binary under
ELECTRON_RUN_AS_NODE=1andnodev22.22.0. Without an encoding the shipped runtime throwsERR_FS_FILE_TOO_LARGEfor the 2.6 GiB file, so the difference is in the string path, not the read.The trap matches the production crash frame for frame: same
EXC_BREAKPOINT, same faulting frames atElectron Framework offset
0x5a698e0.That trap is Electron's or Node's to fix, not DSH's, and it is here because it is what exposes the
lost signal. A crash that writes nothing, in a process whose last stderr line is unrelated, is the
case the signal exists to name.
The reading side already has a pattern in-tree:
dsh-fs-localrefuses an oversized read beforeopening the file (
readWholeBytes:info.size > maxBytesthrowsFS_TOO_LARGE). Plugins that callnode:fsdirectly get no such guard, and their failure mode is the end of the Host process ratherthan a failed tool call. The plugin here was a third-party
check_claimstool, which now measuresbefore reading. The Host-side message is what this report asks to fix.
Reproduction
No large file is needed for the diagnostic defect. Any Host killed by a signal produces it:
apps/desktop/tests/host-process.spec.tscovers the exit-code case already (/crashcallsprocess.exit(7)); the same route withprocess.kill(process.pid, 'SIGKILL')reachescloseascode === null, signal === 'SIGKILL'.dsh desktop host stopped, the same message a Host that exited zero gets,with the child's stderr attached as if it explained the stop.
is the only thing that separates the two cases.
For the real incident, one file still has it:
DeepSeek Harness-2026-10-09-154123.ipsand-160619.ipsin~/Library/Logs/DiagnosticReports, matchingcrash-2026-10-09T07-41-22-424Z-host.logandcrash-2026-10-09T08-06-18-213Z-host.log.Patch
Commit
7b59971f7fon the fork'sfix/host-exit-signalbranch, based on upstreammaster5badb15009.apps/desktop/src/host-process.tstakes thesignalfromcloseand reports it (dsh desktop host was killed by SIGTRAP), and labels the appended stderr as the last output rather than the cause. Theexit-code and clean-stop branches keep their conditions. The spec gains a
/killedfixture route anda test that a signalled child is named by its signal; the existing exit-code test now asserts the
labelled tail. The test uses
SIGKILLrather than the incident'sSIGTRAP, because a trapping childmakes macOS write a crash report on every test run.
Evidence, in a fork clone at
5badb15009with the patch applied:vitest run apps/desktop/tests/host-process.spec.ts: 22 passed.host-process.tsand rerun: 2 failed, 20 passed. The two are theexit-code message and the new signal case.
tsc -b apps/desktop/tsconfig.host.json: exit 0.Options considered
The stderr tail stays. When a Host exits with a real code it is often the whole explanation (
exited with 7; last output on stderr: plugin crashed), and it is capped at 64 KiB; dropping it would tradeone partial answer for another. A separate signal field in the crash report was rejected as
redundant, since the dialog renders the message and the report file renders the error with its
message.
All reactions