DeepSeek Harness 踩坑记录:最初我只是不想把它装到 C 盘,没想到……
|
zhenglin
2026年11月2日 9:31
本文热度 28
|
本文导读:DeepSeek Harness(dsh v0.1.0-rc.7)在 Windows 原生环境下的部署实录。适合 C 盘紧张、磁盘 I/O 敏感的虚拟机,需要把 dsh 完整隔离到独立沙盒的场景。全文按踩坑时间顺序展开,每个坑都附原因与解法。
8 月 13 日,DeepSeek Harness(dsh)开源,star 涨得飞快。看官方 README 里的 Web UI 截图,我挺想上手:一个能在网页里直接聊的 Agent,自己读文件、跑命令、开内嵌终端。但我的环境不太友好——一台 Windows 虚拟机,磁盘 I/O 敏感,大量读写会把它锁死;C 盘用到 84%(剩 30G),E 盘倒是空着 91G。所以目标从一开始就很明确:装进 E 盘的独立沙盒,配置、密钥、profile 全隔离在里面,不碰用户主目录,不碰业务目录。
折腾了一天,服务最后稳稳跑在 http://127.0.0.1:3080。 真实落地体验远没有官方 README 一条命令那么顺滑,我按时间顺序记下全部踩坑过程,给同样想把 dsh 迁出 C 盘的 Windows 用户做参考,也方便自己日后复盘。
六关速览
| 关卡 | 一句话坑 | 一句话解法 |
|---|
| 第一关 | npm 11 解析 peer 依赖确定性卡死 | 换 pnpm |
| 第二关 | pnpm 11 拦截依赖构建脚本 | 白名单写进 pnpm-workspace.yaml |
| 第三关 | 以为要装 VS Build Tools / Python | node-pty 自带预编译,免工具链 |
| 第四关 | 打印地址后进程神秘消失 | cmd 尾随空格坑,改 PowerShell 直接 spawn |
| 第五关 | API Key 输入框被锁 | 启动前屏蔽环境变量 DEEPSEEK_API_KEY |
| 第六关 | DSH_HOME 隔离心里没底 | 源码确认优先级,实测验证 |
想直接抄结论,拉到底部看「写在最后」。
第一关:npm 装上十分钟,CPU 满转,node_modules 永远是空的
官方路线很简单,npx 一条命令。我照做,然后它卡住了——准确说是**"确定性"地卡住**。CPU 满转,node_modules 永远是空的,日志永远停在同一行:
placeDep dsh-client-ui-layout
重来一次,还是这一行。dsh 的客户端组件包之间存在大量互相约束的 peerDependencies,npm 11 解析 peer 依赖时做指数级回溯,直接把自己绕死。两次复现,两次卡死在同一行,是必现,不是偶发。npm --legacy-peer-deps 能绕过 peer 校验,但会引入依赖版本不一致的风险,所以我直接换了 pnpm。
换 pnpm,pnpm add -D @deepseek-ai/dsh@0.1.0-rc.7,一次通过,531 个包。这条路从此写进了我们的手册:别用 npm。
第二关:pnpm 拦下了所有构建脚本,包括 node-pty
pnpm 11 默认拦截依赖的构建脚本,node-pty 这种要编译原生模块的包首当其冲。网上很多教程让往 package.json 的 pnpm 字段写 onlyBuiltDependencies,我照做了,没用——package.json 里的白名单只对 workspace 根项目生效,而我是直接 pnpm add 装远程 npm 包,根本不在 workspace 里。非 workspace 场景,白名单得写在项目根的 pnpm-workspace.yaml,放 5 个包:
onlyBuiltDependencies:
- node-pty
- koffi
- protobufjs
- '@google/genai'
- dsh-subprocess-local
补充:如果你只是直接安装官方发布的 rc.7 版本,这个白名单其实不影响最终运行——rc.7 自带 Windows 预编译二进制,构建脚本跑不跑都能用。它真正起作用的是 git clone 源码本地编译 dsh 的场景。
第三关:以为要装 VS Build Tools,结果 prebuilds 里躺着现成的
node-pty 是个要编译的原生模块,我差点就去装 VS Build Tools + Python——装完 C 盘又得少几个 G。动手前先看了一眼包结构,prebuilds/win32-x64/conpty.node 早就躺在那里了。node-pty@1.2.0-beta.15 自带预编译二进制,随手验一下:
const { spawn } = require('node-pty');
console.log(typeof spawn); // function
返回 function。本地编译工具链,完全不需要。
第四关:日志明明打了地址,进程两分钟就死
装好,启动,控制台正常打出 dsh web: http://127.0.0.1:3080,浏览器也能短暂访问——等到 profile 内部开始安装依赖(pnpm 装 agent preset 那一步)时,进程直接崩溃消失,再刷新就是 404。这个"打印完地址就死"的现象折腾了我们最久。
一开始怀疑是启动方式的问题——dsh.cmd shim 加 cmd /c 加重定向,在 Windows 上后台分离启动本来就不靠谱。后来又怀疑 home 目录没初始化好。查来查去都不对。
最后是清理沙盒时破的案。E:\dsh-sandbox 下面躺着一个叫 home 的目录——注意,目录名末尾带一个半角空格(Markdown 排版下肉眼几乎看不出来)。它创建于 10:57:54,正好是第二次启动的时刻,里面甚至有完整的 profiles/node_modules。而里面所有文件的时间戳,都停在 10:58:30——进程死掉的时刻。
真相水落石出:我们启动脚本里写的是 set DSH_HOME=E:\dsh-sandbox\home ,cmd 的 set 会把行尾空格也算进变量值(set VAR=value 的经典坑),DSH_HOME 于是指向了带空格的 home 。dsh 往这个畸形路径初始化 profile,装到一半崩了。而我们一直在检查真正的 home 目录——它当然是空的,因为 dsh 从头到尾没碰过它。
从此启动不再走 cmd shim,改成 PowerShell 直接 spawn:
$env:DSH_HOME = '<你的沙盒目录>\home'
Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue
# ⚠️ 重要:下面两行请改成本机 node.exe 和 dsh bin 的实际路径
$node = '<你的 node.exe 绝对路径>'
$bin = '<dsh 的 lib/bin.js 绝对路径>'
$p = Start-Process -FilePath $node -ArgumentList $bin,'web','--port','3080' `
-WorkingDirectory '<你的工作区目录>' `
-WindowStyle Hidden -PassThru
提示:Windows 默认执行策略会阻止直接双击运行 ps1 脚本;可以在终端执行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser 放开权限,或者像我一样用一个 cmd 包装调用 ps1。
PowerShell 字符串传值没有尾随空格的问题,这个坑彻底免疫。实测 shell 退出后服务依然活着,HTTP 200。
第五关:API Key 输不进去,界面说"由环境变量提供"
服务跑起来,去设置里填 API Key,输入框是灰的,旁边写着:密钥由环境变量提供。我当时很困惑,我没设过 DEEPSEEK_API_KEY 啊。
扒了源码里 dsh-credentials-local 才看明白,dsh 的凭据解析链是这样的:
进程环境(DEEPSEEK_API_KEY) 只读,优先级最高
> $DSH_HOME/.credentials.yaml 可写,Models 页保存的密钥写这里
> 启动目录/.env 只读兜底
> $DSH_HOME/.env 只读兜底
环境变量提供的凭据,dsh 是故意做成只读的:describe 直接返回 writable:false,任何写入都会被 assertUnshadowed 拒绝。原因很合理——DEEPSEEK_API_KEY=xxx dsh 这种启动方式在 CI 里是显式意图,不该被网页悄悄改掉。而我们这台 VM 的环境里恰好带着一个 DEEPSEEK_API_KEY(进程级的,注册表里查不到,来自上级进程——这种继承是内存级的,不写注册表、不写系统环境变量,重启终端就消失,所以特别难排查),服务一启动就继承了它,界面自然就锁了。
中间差点走弯路:有人建议往 home/config.json 写 {"models":{"deepseek":{"apiKey":""}}}。我翻了整棵依赖树,没有任何代码读 config.json,这个 schema 根本不存在。dsh 的通用配置存在 settings.yaml,但密钥不会进 settings.yaml——密钥只存在环境变量或 .credentials.yaml 里。
正解其实就一行,启动脚本里把环境变量摘掉再 spawn:
Remove-Item Env:DEEPSEEK_API_KEY -ErrorAction SilentlyContinue
只影响 dsh 子进程,不动系统环境。重启之后 Models 页输入框解锁,粘贴保存,密钥落进 E:\dsh-sandbox\home\.credentials.yaml。这个文件还带 watcher,改完热生效,不用重启服务。
顺带一提,环境里原来那把钥匙其实是真的,我们请求了 api.deepseek.com,返回了 deepseek-v4-flash 和 deepseek-v4-pro。这里这样做是为了能独立一个新的api以便和之前的进行区分。
第六关:DSH_HOME 隔离,一开始心里没底
沙盒的核心诉求是隔离:配置、profile、密钥全在 E:\dsh-sandbox\home 里,一删就干净,一打包就能备份。dsh 的 home 路径优先级从源码确认:CLI 的 --home 参数 > DSH_HOME 环境变量 > 默认 ~/.dsh。实测也验证了:设了 DSH_HOME 之后,home 自动建出 profiles/web 和 storages/workspace.json,C 盘用户目录下的 ~/.dsh 再也没被动过。
现在的状态
- 服务在 http://127.0.0.1:3080,稳定。启动统一走
launch-web.ps1(PowerShell 直接 spawn);start.cmd 只是包一层调它,cmd 里不设任何环境变量,尾随空格的坑从源头绕开 - 密钥隔离在 home.credentials.yaml,工作区 E:\dsh-sandbox\ws 已在 Web UI 里选中
- 内嵌终端实测可正常交互(node-pty 的 ConPTY 预编译模块没掉链子)
- 环境钥匙已验证对 v4 模型有效,Models 页可正常输入密钥
到这里整条链路全部打通:Web UI、模型对话、文件读写、shell 命令、内嵌终端,一样不缺。当初担心的 VS Build Tools、WSL2 B 计划,最后都没用上。
写在最后:这些坑的适用范围
回头看,绝大多数坑来自两个叠加条件:Windows 原生 + 自定义 DSH_HOME 指向全新空目录。如果直接用默认路径 ~/.dsh,大部分坑根本不会触发——但代价是配置、profile、密钥全落在 C 盘用户目录,没法做磁盘隔离。rc.7 还是 preview 版本,Windows 自定义 home 属于小众测试路径,网上资料少。如果你 C 盘空间充足,直接走默认 ~/.dsh 是成本最低的选择;这套折腾出来的方案,是给 C 盘紧张、虚拟机环境、需要完整备份迁移沙盒的场景准备的。这份记录就当给同路人趟雷了。
本文实测环境:Windows x64 原生 + Node v24.19.0 + pnpm 11.22.0,dsh v0.1.0-rc.7
阅读原文:点击这里
该文章在 2026/11/2 9:31:17 编辑过