DeepSeek Harness 工作区全消失:workspaceController unavailable 恢复
最容易被误判成「数据全丢了」的一次故障,其实是三件完全不同的事共用一个报错:workspace create failed: gateway/service-unavailable: typert gateway: workspace/create: active Service "workspaceController" is unavailable。 看到这句话就重装、回退版本、新建 profile,基本都不会有用——坏数据还在原处。正确的顺序是:先做零成本、可逆的动作,再按代价往上加(#10885)。
DeepSeek Harness 为什么第一步是「移出去」而不是「修回来」
把可疑目录移出 sessions 根是可逆动作:不删、不改内容,只是让启动扫描不再撞上它。做完重启就能知道数据到底还在不在,代价最低。
- 完全退出 DSH,包括托盘与任务管理器里的残留进程。
- 复制整份
~/.dsh/sessions到另一个盘,作为冷备份。 - 把所有
*.archived-*、改名过的目录、以及你不确定来源的目录,整体移到 sessions 根之外。 - 启动 DSH,看工作区和会话是否回来。
- 回来了就说明数据完好,问题只在扫描环节;没回来再往下走第二、第三优先级。
社区案例的实测结果很硬:把被改名的目录移出后,23 个工作区、867 个会话全部重建,零丢失,824 个 v4 与 43 个旧格式会话都能正常解压。
DSH插件 第一优先级:会话目录被原地改名(#10885)
外部工具把会话目录原地改名归档、又在同一位置新建同名目录时,一个 session-id 血统会对应两份目录,启动扫描撞上它就会让整个列表抛错。
- 回想有没有外部桥接在管会话,比如飞书机器人。这个案例里是桥接因为 SDK 缺
resume_session,自己做了归档:feishu-<id>/→feishu-<id>.archived-<ts>/,然后新建一个<id>/。 - 注意「潜伏」这一段:桌面端连续 6 天没重启,而会话只在启动时扫描,所以问题一直没暴露。
- 触发点是 rc.2→rc.3 夜间自动更新强制重启,第一次完整扫描就撞上了改名目录。
- 失败链路是:
JsonlSessionPersistence.assertStoredIdentity()抛普通Error(corrupt session log ...: header id ... and cwd identify ...),而listArtifacts()只过滤SessionFormatUnsupportedError和SessionPersistenceCorruptionError。 - 普通错误逃出去 → 列表抛错 →
dsh-workspace不激活 →workspaceController不可用。
所以这不是版本问题:社区比对过 rc.2 与 rc.3 的三个相关包,MD5 完全一致,用 rc.2 的命令行对着同一个会话根也能复现同样的报错。
社区在 fork 上给了 12 行补丁(分支 fix/list-survives-relocated-session-logs):列表扫描时(expectedId === undefined)把身份不匹配抛成带类型的 SessionPersistenceCorruptionError,让 listArtifacts 按既有的损坏分支跳过;定向加载仍然抛普通错误。上游未合入。
DeepSeek Harness 第二优先级:workspace.json 通过不了 schema(#10908)
workspace.json 是合法 JSON,但 workspaces 表里任意一条记录缺字段或时间戳不是 ISO 字符串,domain 就打不开,workspaceRegistry 不激活,外壳只显示一个泛化的 service-unavailable。
- 完全退出 App,备份
~/.dsh/storages/workspace.json。 - 打开文件,逐条检查
workspaces表,确认每条都有path、title、sessionIds、createdAt、updatedAt。 - 把时间戳补成 ISO-8601 字符串,例如
2026-01-01T00:00:00.000Z。报错原文就是createdAt: expected string, received undefined。 - 如果确认是废弃记录,直接删掉该条,同时从
global.workspaceIds里移除对应 id。 - 重启 App,单独启动宿主确认 stderr 里不再有
entries did not activate,workspace/create返回ok: true。
这一条的迷惑性在于:新建 profile、回退版本、重装都绕不过,因为坏记录仍在同一个 storage 文件里;它也和凭据、HOME、目录权限无关。
DeepSeek插件 第三优先级:profile 根不可达,启动静默退出(#10678)
桌面版双击没窗口、进程直接退出,日志是 ENOENT ... mkdir ...\.dsh\profiles\desktop,那是 withLock 的第一行 mkdirSync 抛出来的,前面没有任何 try。
- 先确认
DSH_HOME是不是一个指向别处的目录联接(比如G:\deepseek\.dsh指向%USERPROFILE%\.dsh)。 - 检查联接目标是否还存在:在资源管理器里打开联接路径,悬空的联接列不出内容。
- 把之前改名备份的
.dsh.bak改回%USERPROFILE%\.dsh,或重建联接指向它。 - 再启动。恢复正常说明就是这一条。
- 如果本来就不需要这层联接,删掉它、把备份改回原位更省事——多一层联接就多一个失败点。
机制值得记住:recursive: true 的 mkdir 穿过悬空联接时,报的是叶子路径的 ENOENT,不是「联接断了」,所以日志看起来像在抱怨 profiles 目录不存在。
DeepSeek Harness 三种成因怎么分
先看症状形态再动手:报 workspaceController 不可用但窗口能开,查前两条;窗口压根没出来,查第三条。
| 症状 | 关键证据 | 处理优先级 |
|---|---|---|
| 工作区列表空、新建失败 | 会话根里有 *.archived-* 或改名目录 | 移出目录(第一优先级) |
| 同上,且没有改名目录 | ~/.dsh/storages/workspace.json 字段缺失 | 补 schema(第二优先级) |
| 双击无窗口直接退出 | 日志 ENOENT mkdir profiles\desktop | 修联接(第三优先级) |
DSH插件 恢复之后怎么确认真的好了
别只看窗口里有没有列表,要确认「扫描—激活—增删」三步都通了。
- 重启后先数工作区数量,和备份里的目录数量对一遍。
- 随便打开一个旧会话,确认历史消息能正常显示、能继续对话。
- 新建一个工作区,看是否还会返回
workspace create failed。 - 再重启一次,确认状态能保持——如果只在当次进程里正常,说明还有一条坏记录没被移走。
注意事项
- 别先删 sessions。社区里有人建议「全部删掉就能用了」,那等于放弃判断机会,先移出、先备份。
- 别用新建 profile 试。坏数据在同一个 storage 里,新 profile 读的是同一份。
- 别指望回退版本。这条链与版本无关,三个相关包在两个版本间字节一致。
- 归档工具要改行为。任何在
sessions/里原地改名的外部工具,每个归档周期都会再造一次这个状态。 - 重启前先完全退出。多实例会同时扫描,出问题时更难判断是哪一份数据坏的。
如果三步都做完仍然 unavailable,把宿主 stderr 完整抓下来——真实原因不在 UI 那句泛化提示里,只在那份日志里。相关排查见 DeepSeek Harness 会话打不开:turn/end reason 为 null 与重复 id 写坏日志。
常见问题
多数情况没丢,DeepSeek Harness 的工作区记录还在原处:社区案例里把被改名的会话目录移出 sessions 根后,23 个工作区、867 个会话全部重建,824 个 v4 加 43 个旧格式都能正常解压。先备份、先移出,再判断。
因为 DeepSeek Harness 只在启动时扫描会话一次,扫描时 assertStoredIdentity() 抛的是普通 Error,而 listArtifacts() 只过滤两种带类型定义的会话错误。普通错误逃出去,列表整体失败,dsh-workspace 不激活,workspaceController 就一直 pending。
合法 JSON 不等于通过 schema,DeepSeek Harness 的 workspace 记录还要过字段校验。workspaces 表里每条记录必须有 path、title、sessionIds、createdAt、updatedAt,时间戳还得是 ISO-8601 字符串;缺一个就抛 DomainError 加 ZodError,domain 打不开,workspaceRegistry 不激活。
都不能,回退版本、新建 profile、重装 DeepSeek Harness 都绕不过这个报错。坏记录还在同一个 storage 文件里,新 profile 也会读同一份数据;回退版本更没用,社区比对过 rc.2 与 rc.3 的三个相关包 MD5 完全一致。
先在 DeepSeek Harness 里看 DSH_HOME 是不是目录联接且目标被改名或移走。withLock 第一行就是 mkdirSync(profile, {recursive:true}),前面没有 try;穿过悬空联接时报的是叶子路径 ENOENT。把备份目录改回原名或重建联接,再启动。
相关术语
- workspaceController
- workspaceController 是 DeepSeek Harness 桌面端里负责工作区增删查的服务。它依赖 workspaceRegistry 激活,上游任一环节失败时它保持 pending,UI 只会显示 active Service is unavailable。— https://github.com/deepseek-ai/deepseek-harness/discussions/10908
- assertStoredIdentity
- assertStoredIdentity 是 JsonlSessionPersistence 里校验会话文件头 id 与 cwd 推导路径是否一致的方法。目录被原地改名后它会抛普通 Error,从而绕过 listArtifacts 的类型过滤。— https://github.com/deepseek-ai/deepseek-harness/discussions/10885
- workspaceRecord
- workspaceRecord 是工作区存储记录的结构,必须包含 path、title、sessionIds、createdAt、updatedAt 五个字段,时间戳为 ISO-8601 字符串。任一条不满足即触发 schema 校验失败。— https://github.com/deepseek-ai/deepseek-harness/discussions/10908
- 目录联接(junction)
- 目录联接(junction)是 Windows 上指向另一个目录的重解析点,常被用来把 DSH_HOME 放到别的盘。目标被移动或改名后联接悬空,递归 mkdir 会以叶子路径 ENOENT 失败。— https://github.com/deepseek-ai/deepseek-harness/discussions/10678
来源
- #10885 — 0.2.1-rc.3 工作区之前的项目与会话不见了,也无法创建新的· deepseek-ai(GitHub Discussions)
- #10908 — Windows 桌面版无法添加工作区:workspaceController is unavailable· deepseek-ai(GitHub Discussions)
- #10678 — 桌面版 0.2.1-rc.2:profile 根目录不可达时静默退出· deepseek-ai(GitHub Discussions)
- deepseek-ai/deepseek-harness(源码仓库)· GitHub