Bug: service crashes with ENOENT when the %TEMP% spill directory is removed (Windows) #1961
Replies: 4 comments 1 reply
|
Confirmed at source level and implemented as a cherry-pick-ready branch. Source audit (master 47f9438)
Patch: fix/subprocess-spill-recreate-on-enoenthttps://github.com/zoahdev/deepseek-harness/tree/fix/subprocess-spill-recreate-on-enoent
Verification
Thanks for the exact stack trace - the "directory vanished mid-service" case is exactly the kind of thing that only shows up in the wild. |
|
Great - that's exactly the scenario to validate against. One note for your local test: the regression test lives in If anything still surfaces (e.g. a race between the purge and the |
|
Applied the fix branch locally (fast-forwarded to 6fb61d6) and restarted the service - running fine with the fix in place. Will report back after stress-testing the purge scenario. Thanks again! |
|
%TEMP% spill 目录被删 → ENOENT 崩溃——#260(spill I/O 失败终止进程)同族,磁盘清理工具清了 %TEMP% 就踩到。 临时:把 dsh 的临时目录排除出清理范围(和 #758 Storage Sense 删沙箱临时目录同类处理)。第 12 章 Windows 坑位有记录:https://github.com/Electricitysheep/dsh-handbook/blob/main/docs/12-limitations.md |
Uh oh!
There was an error while loading. Please reload this page.
Environment
pnpm install && pnpm run build && pnpm dsh web)Description
On Windows, the whole service process crashes with an uncaught
ENOENTwhenOutputCollector.spillAll()tries to open a spill log file whose parentdirectory no longer exists:
The crash happened while a task in the Web UI was running a subprocess whose
stderr output exceeded the in-memory cap, triggering the spill path.
Root cause analysis
privateSpillDir()(spawn.ts:90) creates adsh-subprocess-*directoryunder
os.tmpdir()viamkdtempSync. It is a module-level singleton andthere is no code path that ever deletes it.
%TEMP%, which system cleanup(Storage Sense / disk cleanup / third-party cleaners) periodically purges of
"unowned" directories. The directory vanished while the service was running.
OutputCollector.spillAll()(spawn.ts:169) assumes the directory exists:it calls
openSync(this.spillFile, 'wx', 0o600)directly, with no existencecheck and no retry.
stream.on('data')handler (spawn.ts:366),so the exception is uncaught and takes down the entire service process.
Note: this is not just a theoretical race. We observed the crash in practice
~20-30 minutes after starting the service; after the crash,
%TEMP%containedno
dsh-subprocess-*directory at all.Steps to reproduce
git clone,pnpm install,pnpm run build,pnpm dsh web.mkdtempSynccreates%TEMP%\dsh-subprocess-*(any Web UI task that runs a command will do).dsh-subprocess-*directory under%TEMP%(or waitfor system cleanup to remove it).
output. The service crashes with the ENOENT above.
Suggested fix
OutputCollector.spillAll()(spawn.ts:169), ensure the spill directoryexists before opening the file — e.g.
mkdirSync(this.spillDir, { recursive: true })(or catch
ENOENTand recreate + retry).(application data dir) so external cleanup cannot remove it mid-run.
datahandler (spawn.ts:366) so an output-collectionfailure degrades gracefully (drop output) instead of killing the process.
Workaround (used to unblock)
Start the service with a dedicated temp directory:
All reactions