DeepSeek Harness Windows spawn 4s slower: pwsh wrapper
echo 1 takes milliseconds in a terminal but 10 seconds end to end in DSH: 4.4 seconds are the hook, and another 6 run from hook end to the tool result. Once that baseline exists, the problem is pinned to "every spawn pays a fixed cost", not the model or the network (#10608). Four timing segments narrow it down step by step.
DeepSeek Harness: Setup: use echo 1 as the baseline
Pick the command least likely to be slow, measure its end-to-end time, then break down where the time goes.
- In a session, have the agent run
echo 1and record the wall time fromtool/calltotool/result. - Community result: about 10 seconds, of which 4.4 were the PreToolUse hook and about 6 ran from hook end to result.
- Run the same command in a terminal and it returns almost instantly.
- Conclusion: the call chain is slow, not the command content.
DSH plugin: Segment 1: time tools that do not spawn
read and grep start no subprocess; if they are also slow, the overhead is around the call rather than inside the tool.
- Aggregate tool durations over a window: in the community sample
readran 49 times averaging 4,860 ms, andgrep11 times averaging 4,309 ms. - Compare with the hook average: PreToolUse ran 324 times averaging 4,046 ms.
- The two almost coincide, so read/grep time is essentially hook time.
pwshaveraged 10,621 ms, and the extra is the pwsh process starting itself.mcp__…saveaveraged 4,251 ms; MCP subprocesses pay the same fixed cost.
| Tool | Spawns? | Average |
|---|---|---|
read | No | 4,860 ms |
grep | No | 4,309 ms |
pwsh | Yes | 10,621 ms |
mcp__…save | Yes | 4,251 ms |
This segment rules out "a slow tool implementation": non-spawning tools are just as slow.
DeepSeek Harness: Segment 2: isolate the hook and measure it alone
Make the hook script exit on its first line; if the duration does not change, the cost is not in the script.
- Measure the four hooks in
hooks.json: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse. - Record the
hook/result.durationMsDSH reports: PreToolUse averages 4,046 ms, PostToolUse 4,024 ms, UserPromptSubmit 4,310 ms. - Run the same scripts manually in a terminal: 206 ms, 51 ms, 51 ms.
- Run one more control: make
block_delete.jsstart withprocess.exit(0)and do nothing. - DSH still records 4,298-4,429 ms, with only 131 ms of variation across 8 consecutive calls.
- Conclusion: this is a fixed cost unrelated to script workload.
DSH Plugin: Segment 3: measure shell startup itself
If a shell starts in 0.2 seconds but the call takes 4, the extra time lives in the wrapper and process creation outside the shell.
- Time them in a terminal:
pwsh -NoProfile -Command exittakes about 190 ms,pwsh -Command exitabout 198 ms. powershell.exe5.1 takes about 132-149 ms,cmd /c exitabout 31 ms,node -e 0about 38-48 ms.-NoProfilesaves only 8 ms, so the "profile or module autoload is slow" hypothesis is out.- In other words, no single shell startup comes anywhere near 4 seconds.
- The cost equals that startup plus everything the runner does around it, paid every time.
DeepSeek Harness: Segment 4: inspect the process tree for the middle layer
Open the command line column in Task Manager or a process viewer and see who the parent of node.exe is — this step exposes the wrapper directly.
- Trigger one hook call and watch the process tree at the same time.
- Community finding:
node.exeis not a direct child of DSH but a grandchild under the pwsh wrapper. - Match it with the source shape: DSH wraps commands uniformly as
pwsh -NoLogo -NoProfile -NonInteractive -Command "<command>". - For commands shaped as "absolute path + arguments", the wrapper is pure overhead.
- The four segments now close: the tool is fine, the hook script is fine, shell startup is fine, and the problem is the wrapper.
DSH plugin: Fix: call CreateProcessW when the command shape is simple
The community patch makes the executor's argv() attempt a simple split first, calling CreateProcessW directly when possible and falling back to the original path otherwise.
- Only commands shaped as "absolute path + arguments" take the direct path.
- Anything containing pipes, redirection,
$, backticks,#, newlines, smart quotes, unbalanced quotes, a relative first token,.cmd/.bat/.ps1, or--%falls back. - Timeout, buffering, and exit code handling are untouched.
- Measured on the same machine and version after the patch: PreToolUse dropped from 4,046 ms to 240-255 ms;
readto 144-177 ms;grepto 362-395 ms; a python MCP call to 151 ms. - Note that 240-255 ms covers only spawn plus script execution and excludes about 200 ms waiting for the output stream to close, so the whole hook is about 440 ms.
- About 1.3 seconds of waiting still sits in
NtCreateUserProcess, below the JS layer, and this patch cannot reach it.
Version status and what you can do
The wrapper is still present on rc.3, so commands using shell syntax still take 4.2-4.7 seconds; all you can do is reduce how often it triggers.
- On 0.2.1-rc.3,
argv()still unconditionally returns the pwsh wrapper; the community measured 0.4-0.6 seconds for the direct-spawn path and 4.2-4.7 seconds for shell commands. - A
python.exe -c "…"with quoted arguments is judged back to the shell by the scanner and still measures 4,506 ms. - What you can do: reduce the number of hooks and how often they fire, especially PreToolUse.
- Write hook commands as "absolute path + arguments" where possible, avoiding pipes and redirection.
- Do not go add Defender exclusions — the community neither tried nor needed it, and the real cause is the wrapper.
- A complete fix needs upstream to route hooks through
ctx.subprocess, or to skip the shell when the command is already argv.
Cautions
Time each layer before blaming the model or the machine.
- Do not mistake a fixed delay for network jitter. Eight calls varied by only 131 ms; this is a fixed cost.
- Do not suspect the profile first.
-NoProfilesaves only 8 ms. - Do not look only at hooks. The
pwshtool also stacks its own startup on top. - The patch is not a release build. When reporting, state the version and whether the patch is applied.
- Record the process tree. It is the fastest picture separating "slow tool" from "slow wrapper".
If your machine shows "a few seconds occasionally" rather than "a few seconds every time", it is more likely sandbox or ACL related — change direction.See DeepSeek Harness slow startup: causes and fixes for dsh for the related case.
FAQ
The slowdown comes from the DeepSeek Harness tool layer, not the model. The community used echo 1 as a baseline: 10 seconds end to end, of which about 4.4 were the PreToolUse hook and another 6 ran from hook end to tool result. echo 1 itself takes milliseconds.
read and grep are slow in DeepSeek Harness because they pass through a PreToolUse hook on every call, and the hook itself spawns a process. The community measured read at 4,860 ms and grep at 4,309 ms on average, almost identical to the hook's 4,046 ms.
To prove the slowness is not the hook script itself in DeepSeek Harness, make the script exit on its first line with process.exit(0), doing nothing, and the duration DSH records is still 4,298-4,429 ms; the same script run manually in a terminal takes only 51-206 ms. The gap is in the wrapper layer, not the script.
The middle layer is the pwsh wrapper in DeepSeek Harness. DSH wraps every command as pwsh -NoLogo -NoProfile -NonInteractive -Command "<command>", and node.exe is actually a grandchild of pwsh. Removing that wrapper drops hooks to 240-255 ms.
No, the official DeepSeek Harness build was not verified to contain the fix, because the community patch was only tested on their own build. On 0.2.1-rc.3, argv() still unconditionally returns the pwsh wrapper, commands using shell syntax still take 4.2-4.7 seconds, while the direct-spawn path is 0.4-0.6 seconds.
Related Terms
- PreToolUse hook
- A PreToolUse hook is a Claude Code-style hook that runs before every tool call. DSH runs it through a shell, so it introduces additional process startup overhead.— https://github.com/deepseek-ai/deepseek-harness/discussions/10608
- pwsh wrapper
- The pwsh wrapper is how DSH on Windows packages commands uniformly as pwsh -Command before handing them to the executor. For commands already shaped as an absolute path plus arguments, this layer is pure overhead.— https://github.com/deepseek-ai/deepseek-harness/discussions/10608
- CreateProcessW
- CreateProcessW is the low-level Windows process creation API. The community patch calls it directly when the command shape is simple, bypassing the shell, and otherwise falls back to the original wrapper path.— https://github.com/deepseek-ai/deepseek-harness/discussions/10608
- NtCreateUserProcess
- NtCreateUserProcess is the system call CreateProcessW uses internally to create a process. The community observed about 1.3 seconds of remaining wait parked there, below the JS layer.— https://github.com/deepseek-ai/deepseek-harness/discussions/10608
Sources
- #10608 — Every subprocess spawn costs 4-6s on Windows· deepseek-ai (GitHub Discussions)
- deepseek-ai/deepseek-harness (source repository)· GitHub