排查 dsh Web Profile 中的 landlock-run ENOENT,最后发现问题不在 Landlock

这次排查的起点很简单。

在使用 dsh 的 Web 模式时,曾经出现过类似下面的信息:

spawn .../landlock-run ENOENT

第一反应通常会落在几个方向上:

  • landlock-run 文件不存在
  • systemd 用户会话异常
  • sandbox 配置错误
  • Web Profile 和 Headless Profile 行为不一致

后面的检查基本围绕这几个假设展开。

随着证据一点点补齐,最后得到的结论反而很朴素。

当前机器上的 Landlock、systemd user、dsh sandbox 以及 Web Profile 本身都工作正常,真正需要找的是那一次历史会话里为什么出现了 ENOENT。

先确认 systemd user 环境是否正常

dsh 的 subprocess 会通过 systemd user scope 拉起隔离进程。

先检查用户级 systemd 是否正常运行。

systemctl --user is-system-running

返回结果:

running

继续确认运行时目录和 DBus 地址。

echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS"

输出:

XDG_RUNTIME_DIR=/run/user/0
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus

这两个变量都已经存在。

随后直接测试 systemd user scope 能否正常创建。

systemd-run --user --scope --quiet --collect --expand-environment=no \
  --unit=dsh-test \
  -- /usr/bin/node -e 'console.log("SYSTEMD_RUN_OK")'

echo "exit=$?"

结果:

SYSTEMD_RUN_OK
exit=0

如果这里失败,后面很多排查都没有意义。

至少到这一步,可以确认 dsh 依赖的 systemd user 环境没有问题。


用 Headless Profile 验证 subprocess 能否真正启动

下一步直接绕开 Web Profile。

使用 Headless Profile 执行一段最简单的 Bash。

dsh --profile headless \
  'printf "USER=%s\n" "$(whoami)";
   printf "SHELL=%s\n" "$SHELL";
   command -v bash;
   echo HELLO'

echo "exit=$?"

返回内容:

USER=root
SHELL=/bin/bash
/usr/bin/bash
HELLO

exit=0

这里验证了几件事:

检查项 结果
Bash 可执行文件存在 成功
用户身份正常 root
Shell 环境正常 /bin/bash
subprocess 可以执行命令 成功

如果 landlock-run 真不存在,这一步通常就会直接报错。


Web Profile 的行为和 Headless 完全不同

接下来尝试同样的命令。

dsh --profile web \
  'printf "USER=%s\n" "$(whoami)";
   printf "SHELL=%s\n" "$SHELL";
   command -v bash;
   echo WEB_TEST_OK'

返回:

error: too many arguments.
Expected 0 arguments but got 1

再次测试:

dsh --profile web \
  'id; /usr/bin/env bash -c "echo CHILD_BASH_OK; command -v bash; id"'

结果仍然相同:

error: too many arguments.
Expected 0 arguments but got 1

这一步其实说明了一件容易忽略的事情。

Web Profile 根本不是一个命令执行入口。

查看帮助信息后可以确认这一点。

dsh --profile web --help

输出:

Usage: dsh --profile web [options]

Serve the DeepSeek Harness browser UI.

它启动的是 Web UI 服务,不接受命令字符串作为参数。

因此这里的报错与 sandbox 无关,只是 CLI 用法不匹配。


把 Web Profile 配置完整导出来

为了确认 Web 和 Headless 是否使用了不同的 Sandbox 组件,导出配置。

dsh --profile web --dump-config > /tmp/dsh-web-config.txt

重点检查 sandbox 相关插件。

配置中出现:

- id: subprocess
  name: '@deepseek-ai/dsh-subprocess-local'

- id: sandbox
  name: '@deepseek-ai/dsh-sandbox-local'

- id: sandbox-policy
  name: '@deepseek-ai/dsh-sandbox-policy'

- id: bash-sandbox
  name: '@deepseek-ai/dsh-bash-sandbox'

同时还能看到:

mode: workspace-write

以及权限预设:

read-only
workspace-write
danger-full-access

从配置角度看,Web Profile 并没有切换到另一套 sandbox 实现。

使用的仍然是:

dsh-subprocess-local
dsh-sandbox-local
dsh-bash-sandbox

这和 Headless Profile 的关键组件是一致的。


检查 Profile 是否存在额外覆盖

继续查看 Profile 文件。

sed -n '1,260p' /root/.dsh/profiles/web/cordis.yml

结果:

[]

查看补丁层:

sed -n '1,260p' /root/.dsh/profiles/web/cordis.patch.yml

结果:

[]

这说明当前 Web Profile 没有用户自定义覆盖。

不存在某个 patch 把 sandbox runner 换掉的情况。


确认实际运行的 Web 服务

查看正在运行的 dsh 进程。

pgrep -af dsh

结果:

800 node /usr/bin/dsh web --no-open --trusted-host kenfan0108.vip

进一步查看命令行。

tr '\0' ' ' < /proc/800/cmdline

得到:

node /usr/bin/dsh web --no-open --trusted-host kenfan0108.vip

说明当前 Web 服务确实是这一个进程。


最关键的证据来自 journalctl

真正有价值的信息出现在 systemd 日志里。

查询最近日志。

journalctl --user --since "2 hours ago" --no-pager \
  | grep -Ei \
    'dsh|ENOENT|landlock|sandbox|runner|subprocess|bash'

日志中出现了多次 subprocess 启动记录。

例如:

Started dsh-subprocess-8634-9c10d71c9244.scope -
/usr/bin/node
/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-subprocess-local/lib/runner.js
--
/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/landlock-run
--
bash -c "echo HELLO"

以及:

Started dsh-subprocess-8712-bcceb363dfa8.scope -
...
landlock-run
...
bash -c "printf \"USER=%s\n\" ..."

这里已经能确认几个关键事实。

事实一

landlock-run 文件存在。

因为 systemd 已经成功执行到该路径。

事实二

Node 的 spawn 能找到 landlock-run。

否则不会进入 scope。

事实三

Landlock 能实际创建隔离环境。

日志里已经看到:

--ro /
--rw /tmp
--rw /root

这些参数已经被成功传入。

事实四

Bash 能在 Landlock 环境里执行。

因为对应命令已经运行结束。


当前证据能得到什么结论

截至排查结束,已经验证成功的项目如下。

Landlock kernel             OK
landlock-run ELF            OK
Node spawn landlock-run     OK
landlock --probe            OK
Landlock confinement        OK
systemd-run --user          OK
headless profile            OK
web profile sandbox config  OK
web subprocess startup      OK

这意味着最初看到的:

spawn .../landlock-run ENOENT

大概率不是当前环境持续存在的问题。

更像是某一次具体 Session、Subagent 或历史工具调用里的异常记录。


下一步应该查什么

此时继续改配置已经没有意义。

更合理的方向是回到历史数据。

重点搜索:

grep -RniE \
  'spawn .*landlock-run|landlock-run.*ENOENT|ENOENT' \
  /root/.dsh

同时检查 Session 目录:

find /root/.dsh/sessions \
  -type f \
  \( -name '*.jsonl' -o -name '*.log' -o -name '*.ndjson' -o -name '*.json' \)

以及 Subagent 历史记录:

grep -RniE \
  'ENOENT|landlock-run|node-addon-system-linux-x64' \
  /root/.dsh/subagent-gc-backup

因为当前系统已经能够稳定创建:

systemd scope
→ dsh-subprocess-local
→ landlock-run
→ bash

剩下真正需要定位的,是当时记录下 ENOENT 的那份历史上下文。

操作清单速览

  • [ ] 验证 systemctl --user is-system-running
  • [ ] 检查 XDG_RUNTIME_DIR
  • [ ] 检查 DBUS_SESSION_BUS_ADDRESS
  • [ ] 验证 systemd-run --user
  • [ ] 用 Headless Profile 测试 Bash 执行
  • [ ] 导出 Web Profile 配置
  • [ ] 检查 cordis.patch.yml
  • [ ] 确认 Web 服务实际运行进程
  • [ ] 从 journalctl 提取 subprocess 日志
  • [ ] 在 Session 与 Subagent 历史记录中搜索 ENOENT

FAQ

Web Profile 为什么执行不了命令?

因为 dsh --profile web 的作用是启动 Web UI 服务,本身不接受命令字符串作为执行参数。

too many arguments 是 sandbox 报错吗?

不是。CLI 参数解析阶段就失败了,还没有进入 sandbox。

当前机器上的 landlock-run 存在吗?

存在。systemd 日志已经显示其完整路径并成功执行。

Node 能成功 spawn landlock-run 吗?

能。日志里已经出现多个成功启动的 subprocess scope。

Headless Profile 是否正常?

正常,可以执行 Bash 命令并返回结果。

Web Profile 是否使用不同的 Sandbox 实现?

从导出的配置看,没有。核心组件仍然是 dsh-subprocess-local、dsh-sandbox-local 和 dsh-bash-sandbox。

是否需要重装 dsh?

目前没有证据支持这一操作。

当前最值得排查的方向是什么?

搜索历史 Session、Subagent 和日志文件,找到最初记录 landlock-run ENOENT 的原始上下文。