DeepSeek Harness macOS Dock 图标重复:939 个 session.lock 不是泄漏

故障排查发布于 2026-10-30作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSHmacOSDocksession.lock单实例锁文件
DeepSeek Harness 在 macOS 上 Dock 图标越开越多,同时 ~/.dsh/sessions 下堆了 939 个 session.lock。前者是真缺陷(缺单实例锁),后者是正常设计,按锁文件语义拆开看再决定要不要清理。

看到「939 个 session.lock 从不回收」,几乎所有排查思路都会指向「锁泄漏」,然后顺手写个清理脚本——但这一步恰好会把清理做成并发 bug。 同一份报告里还有另一半:Dock 里越堆越多的图标,那才是真缺陷(#10757)。把两句话拆开读,结论会完全不同。

DeepSeek Harness 第一句的语义:「939 个锁文件」等于什么

它只说明这个工作区里被创建过 939 个会话,不说明有 939 个进程活着,也不说明有 939 把锁被持有。

  1. 数一遍:find ~/.dsh/sessions -name "session.lock" | wc -l,社区结果 939。
  2. 看时间分布:最早的是一个月前,其中 09-26 到 09-28 三天分别是 89、115、146,这种台阶更像启动循环,不像自然的会话增长。
  3. 看单个文件:0 字节,和活跃会话的锁长得一模一样——这正是它「看起来像泄漏」的原因。
  4. 看真实活跃度:当天只有 3 个工作区目录被碰过,其中只有一个会话是活跃的。
  5. 所以数量与活跃进程无关,只与历史会话数有关。

DSH插件 锁文件的真实语义:文件在,锁就可能在

POSIX 锁文件按设计永不删除:留下的文件保持稳定 inode,后续加锁者靠它校验是不是同一个对象;真正仲裁归属的是内核,不是文件本身。

  1. 持有者退出后,内核释放锁,但文件仍在——这是「已释放」,不是「泄漏」。
  2. 删掉文件会换掉 inode,后续加锁者校验时看到的就不是同一个对象。
  3. 删掉一个正在使用中的会话锁,等于放弃它存在的意义:互斥。
  4. 社区对此的态度很明确:这种「清理插件」看起来像打扫,行为上等于 mutex bug。
  5. 如果确实嫌文件多,正确做法是内核提供有界目录或明确的保留规则,而不是在使用点删除。

DeepSeek Harness 第二句的语义:「Dock 一排图标」等于什么

它说明每次启动都产生了新进程,系统把它们当成不同实例;缺的是主进程里的单实例保护。

  1. 先排除安装态:ls -d /Applications/*Harness*.app | wc -l 结果是 1,只有一份 app。
  2. 再排除固定项:defaults read com.apple.dock persistent-apps | grep -c "com.deepseek.dsh" 结果是 1。
  3. ~/Applications、/Users/Shared、桌面都没有第二份,桌面也没有启动器。
  4. 查 Info.plist:LSMultipleInstancesProhibited 出现 0 次,LSUIElement 也没有。
  5. 于是 LaunchServices 对每次双击都新起一个进程,每个进程各占一个 Dock 图标。
  6. 同一个根因还解释了另一个旧症状:退出后要点两次图标才能重新打开。

DeepSeek插件 为什么这两件事被写在同一份报告里

它们共享「重复启动」这个触发条件,但一个是运行态表现,一个是文件系统残留,处置方式相反。

维度Dock 重复图标session.lock 累积
是不是缺陷是否,属设计
直接证据单份 app、单个固定项、缺单实例键文件 0 字节、持有者已退出
能清理吗重启后会再次出现不该清理
修复归属核心主进程核心(保留策略)或不动

这也是为什么社区回复只承接第二半:把两件事混在一起,就会得出「删锁能减少 Dock 图标」这种错误因果。

DeepSeek Harness 三十秒自查:把两个数字分开看

两个命令就够:一个数安装态,一个数运行态;安装态永远是 1,运行态会变,但两者都不该被当成「锁泄漏」的证据。

bash
# 1. 安装态:应该只有 1
ls -d /Applications/*Harness*.app | wc -l
defaults read com.apple.dock persistent-apps | grep -c "com.deepseek.dsh"

# 2. 运行态:会随启动次数增长
find ~/.dsh/sessions -name "session.lock" | wc -l

# 3. 单实例声明:缺了就会每次新起进程
/usr/libexec/PlistBuddy -c "Print :LSMultipleInstancesProhibited" \
  "/Applications/DeepSeek Harness.app/Contents/Info.plist"

第 3 条如果报 Does Not Exist,就与社区的观察一致:单实例保护没有声明,重复启动会直接产生新进程。

DSH插件 能做的与不能做的

能做的:减少重复启动、按需清理桌面快捷方式;不能做的:删锁、指望插件修单实例。

  1. 观察期先确认现象:连续几天从 Dock 双击启动,记录图标数量与 find ... session.lock | wc -l 的变化。
  2. 不要写脚本批量删 session.lock,尤其不要在应用运行时删。
  3. 如果想临时缓解图标堆积,退出所有实例后重新打开一次,Dock 会回到单个图标。
  4. 桌面与 Dock 上只保留一个启动入口,避免无意中双击多次。
  5. 真正的修复需要主进程调用 app.requestSingleInstanceLock(),未获得锁就退出,或在 second-instance 事件里 focus() 已有窗口。
  6. 也可以在 Info.plist 里声明 LSMultipleInstancesProhibited,让 LaunchServices 层面直接限制。
  7. 锁文件这一半若要改进,应由核心在启动时清扫「持有者 PID 已死且无活跃写入者」的锁,而不是让用户手工删。

注意事项

  1. 别把「0 字节」当成「空文件可删」。活跃锁与废弃锁在文件层面无法区分,这正是它不可随手清理的原因。
  2. 别用图标数量反推进程数量。Dock 图标的累积是历史启动的结果。
  3. 别指望插件修 Dock。第二次启动发生在任何插件加载之前。
  4. 记录版本与系统。社区报告基于 0.2.0-rc.2,行为可能随版本变化。
  5. 区分「锁文件多」和「锁等待超时」。后者是另一个问题,错误里只会给出路径,同样不该靠删文件解决。

如果你的 Dock 图标重复只在装了多份 app 时出现,那属于安装态问题,按卸载多余副本处理即可。相关排查见 DeepSeek Harness 桌面端启动异常:没有窗口、启动崩溃、EMFILE 与 80 秒单核满载。

常见问题

~/.dsh/sessions 下有 939 个 session.lock,是 DeepSeek Harness 的锁文件泄漏吗?

不是,DeepSeek Harness 的 session.lock 按设计永不删除,它保持同一个 inode 供后续加锁者校验,真正仲裁的是内核而不是文件是否存在。零字节且持有者已退出,表示锁已释放,不表示锁被泄漏。

这些 session.lock 锁文件可以直接在 DeepSeek Harness 中删掉吗?

不建议,删掉 DeepSeek Harness 正在使用的会话锁会放弃它本该提供的互斥保护,等于把清理做成了并发 bug。如果只是嫌多,应该由内核提供有界目录或保留策略,而不是在使用点删除。

为什么 DeepSeek Harness 的 Dock 里会出现一排一模一样的图标?

因为 DeepSeek Harness 没有单实例保护。Info.plist 里没有声明 LSMultipleInstancesProhibited,LaunchServices 对每次 open 或双击都会启动一个新进程,于是每个进程在 Dock 里各占一个图标。

Dock 重复是装了两份 DeepSeek Harness,还是多加了固定项?

社区核对过都不是 DeepSeek Harness 装了两份:/Applications 下只有一份 app,Dock 的 persistent-apps 里也只有一个 com.deepseek.dsh 固定项。Dock 的重复来自运行态,不是安装态。

有没有插件能修好 DeepSeek Harness 的 Dock 重复图标?

不能,DeepSeek Harness 插件管不到单实例:第二次启动在任何插件加载之前就已经发生,单实例保护必须由主进程在启动早期完成,属于核心决策。

相关术语

session.lock
DeepSeek Harness 会话目录下的 POSIX 锁文件是用于保证同一会话只有一个写入者的锁(session.lock)。文件按设计不删除,持有者退出即视为锁已释放。— https://github.com/deepseek-ai/deepseek-harness/discussions/10757
LSMultipleInstancesProhibited
LSMultipleInstancesProhibited 是 macOS Info.plist 的布尔键,禁止同一应用启动多个实例。缺失时 LaunchServices 会为每次打开动作启动新进程,而不是激活已有窗口。— https://github.com/deepseek-ai/deepseek-harness/discussions/10757
LaunchServices
LaunchServices 是 macOS 负责注册与启动应用的系统服务。它依据 Info.plist 声明决定是复用现有实例还是新起一个进程,因此单实例行为需要在这里声明。— https://github.com/deepseek-ai/deepseek-harness/discussions/10757
inode
inode 是文件系统里标识一个文件实体的编号。锁文件保持同一 inode 才能让后续加锁者确认自己锁的是同一个对象,因此这个文件不能随手删。— https://github.com/deepseek-ai/deepseek-harness/discussions/10757

来源