claude-bulkhead 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.es.md +142 -50
  3. package/README.ja.md +142 -50
  4. package/README.ko.md +143 -51
  5. package/README.md +142 -50
  6. package/README.pt-BR.md +142 -50
  7. package/README.zh-CN.md +154 -62
  8. package/hooks/hooks.json +68 -8
  9. package/package.json +5 -2
  10. package/src/agent-hook.js +162 -0
  11. package/src/agent-setup.js +256 -0
  12. package/src/agents.js +89 -0
  13. package/src/allow-cli.js +65 -0
  14. package/src/allow-hint.js +62 -0
  15. package/src/allow-once.js +152 -0
  16. package/src/auto-queue.js +93 -0
  17. package/src/bash-guards.js +36 -0
  18. package/src/basics.js +46 -27
  19. package/src/checkout-guard.js +342 -74
  20. package/src/cli-util.js +13 -1
  21. package/src/cli.js +40 -4
  22. package/src/config-check.js +21 -4
  23. package/src/config-cli.js +75 -10
  24. package/src/config.js +106 -9
  25. package/src/dev-servers.js +361 -61
  26. package/src/disk-guard.js +8 -8
  27. package/src/doctor-enforcement.js +273 -0
  28. package/src/doctor.js +19 -9
  29. package/src/edit-guard.js +196 -0
  30. package/src/events.js +30 -5
  31. package/src/explain.js +251 -0
  32. package/src/git-info.js +55 -0
  33. package/src/gitargs.js +82 -0
  34. package/src/hook.js +38 -6
  35. package/src/kill-extras.js +219 -0
  36. package/src/kill-guard.js +228 -77
  37. package/src/kill-owners.js +116 -0
  38. package/src/log-cli.js +15 -10
  39. package/src/mcp.js +131 -0
  40. package/src/memory-guard.js +126 -24
  41. package/src/memory.js +21 -2
  42. package/src/notices.js +95 -0
  43. package/src/orphan-reaper.js +58 -11
  44. package/src/port-cli.js +78 -3
  45. package/src/port-guard.js +80 -68
  46. package/src/port-kills.js +198 -0
  47. package/src/pressure.js +75 -0
  48. package/src/processes.js +44 -12
  49. package/src/scan-report.js +166 -0
  50. package/src/scan.js +87 -18
  51. package/src/secrets-guard.js +301 -0
  52. package/src/self-allow.js +73 -0
  53. package/src/session-info.js +121 -0
  54. package/src/session-state.js +176 -0
  55. package/src/sessions.js +109 -20
  56. package/src/shell.js +279 -19
  57. package/src/slot-cli.js +92 -21
  58. package/src/slots.js +61 -13
  59. package/src/stable-port.js +61 -0
  60. package/src/status.js +55 -17
  61. package/src/statusline.js +45 -16
  62. package/src/subagent-guard.js +24 -0
  63. package/src/top.js +36 -11
  64. package/src/wait-guard.js +96 -32
  65. package/src/worktree-cli.js +454 -0
  66. package/src/worktree-copy.js +82 -0
package/README.zh-CN.md CHANGED
@@ -4,7 +4,9 @@
4
4
  <strong>阻止 Claude Code 对你其他的开发服务器执行 <code>pkill node</code>、抢占它们的端口,以及用 <code>git add -A</code> 提交另一个会话的修改。</strong>
5
5
  </p>
6
6
 
7
- <p align="center">这组 hook 清楚哪些进程、端口和 git 工作树属于哪个 Claude Code 会话和子代理,只拦截会伤到其他东西的命令,并告诉 Claude 应该改用哪条安全的命令。零依赖,无遥测。</p>
7
+ <p align="center">这组 hook 清楚哪些进程、端口和 git 工作树属于哪个 Claude Code 会话和子代理,只拦截会伤到其他东西的命令,并告诉 Claude 应该改用哪条安全的命令。</p>
8
+
9
+ <p align="center">支持 Windows、macOS 和 Linux。不需要 tmux,不需要 sudo,没有常驻后台进程,不发起网络请求,零依赖。也能保护 Codex、Gemini CLI、Copilot CLI、Cursor 等另外六种代理。</p>
8
10
 
9
11
  <p align="center">
10
12
  <a href="https://github.com/dooly6276/claude-bulkhead/actions/workflows/test.yml"><img src="https://github.com/dooly6276/claude-bulkhead/actions/workflows/test.yml/badge.svg" alt="tests"></a>
@@ -34,7 +36,14 @@
34
36
  /bulkhead:scan 30d
35
37
  ```
36
38
 
37
- `PATH` 里需要有 Node 18 或更高版本(Claude Code 的原生安装程序不会装 Node,请用 `node --version` 确认)。要使用终端命令和状态栏,还需运行 `npm install -g claude-bulkhead`,也可以不安装,直接用 `npx claude-bulkhead status`。
39
+ `PATH` 里需要有 Node 18 或更高版本(Claude Code 的原生安装程序不会装 Node,请用 `node --version` 确认)。
40
+
41
+ | 安装方式 | 你会得到什么 |
42
+ |---|---|
43
+ | 插件(见上) | 对每一次 Bash、PowerShell、Read 和 Edit 调用生效的 guard;斜杠命令 `/bulkhead:scan`、`status`、`log`、`doctor`、`reap`、`remote`;教 Claude 养成安全习惯的 skill。真正保护你的是这一部分。 |
44
+ | `npm install -g claude-bulkhead`(可选) | 在你自己的终端里使用 `bulkhead` 命令(`status`、`top`、`explain`、`allow`、`worktree`、`scan`、`doctor`)、状态栏、`bulkhead-slot`,以及为 Codex、Gemini 等代理配置的 `bulkhead setup <agent>`。也可以不安装,直接用 `npx claude-bulkhead status`。 |
45
+
46
+ 不确定它有没有在工作?`bulkhead doctor` 会让一条无害的命令经过每个 guard,显示它是否被拦下,以及耗时多少。
38
47
 
39
48
  ## 它拦截什么
40
49
 
@@ -42,7 +51,7 @@
42
51
  |---|---|
43
52
  | 代理用 `pkill node` 重启自己的开发服务器,其他 worktree 的服务器也一起挂掉。 | kill-guard 拦下这条命令,并告诉代理如何找到并只结束自己的 PID。 |
44
53
  | 两个会话共用一个检出目录。其中一个运行 `git add -A && git commit`,另一个会话写到一半的改动就进了这次提交。 | checkout-guard 拦下它,并要求写出具体文件名。 |
45
- | 两个 worktree 都执行 `npm run dev`。第二个换到了 3001,但它的代理还在测 `localhost:3000`,看到的是另一个分支。 | port-guard 拦下第二次启动并给出空闲端口。`bulkhead port` 也能随时给出一个。 |
54
+ | 两个 worktree 都执行 `npm run dev`。第二个换到了 3001,但它的代理还在测 `localhost:3000`,看到的是另一个分支。 | port-guard 拦下第二次启动并给出空闲端口。 |
46
55
  | 三个会话在同一分钟里启动 `next build`、`npm test` 和 Playwright,机器卡死。 | 内存不足时 memory-guard 拦截重命令。通过 `bulkhead-slot`,第三个任务会排队,等有空间再运行。 |
47
56
 
48
57
  <details>
@@ -87,7 +96,7 @@ Claude Code 的 hook 是 Claude Code 在每次执行 shell 命令之前运行的
87
96
  /bulkhead:scan 30d
88
97
  ```
89
98
 
90
- 它只读取,输出前会遮盖看起来像密钥的内容,也不会把任何东西发到任何地方。port-guard 和低内存检查不会回放,因为会话记录里没有当时用了哪些端口、占了多少内存;checkout-guard 则根据那一分钟里哪些会话在同一个 git 工作树里活动来重建。详情:[docs/commands.md](docs/commands.md#bulkhead-scan)。
99
+ 它只读取,输出前会遮盖看起来像密钥的内容,也不会把任何东西发到任何地方。port-guard 和低内存检查不会回放,因为会话记录里没有当时用了哪些端口、占了多少内存;checkout-guard 则根据那一分钟里哪些会话在同一个 git 工作树里活动来重建。`bulkhead scan --share` 会输出 5 到 8 行只含数量的摘要(不含路径,不含命令),可以直接贴到帖子或聊天里;`--explain 1` 会完整展示一个示例,并标出匹配的部分。详情:[docs/commands.md](docs/commands.md#bulkhead-scan)。
91
100
 
92
101
  ## 为什么需要它
93
102
 
@@ -115,10 +124,13 @@ claude-bulkhead 是一组小 hook,分别拦下或清理上面这些情况。
115
124
  |---|---|
116
125
  | **[kill-guard](docs/guards/kill-guard.md)**<br>拦截 `pkill node`、`taskkill /IM`、`Stop-Process -Name` 等按程序名结束进程的命令。按 PID 结束的命令放行。 | **[port-guard](docs/guards/port-guard.md)**<br>拦截在其他会话已占用的端口上启动开发服务器,以及按端口结束别人服务器的命令。 |
117
126
  | **[memory-guard 与 bulkhead-slot](docs/guards/memory-guard.md)**<br>内存不足时拦截重命令,并让重任务在所有会话共享的内存预算里排队。 | **[checkout-guard](docs/guards/checkout-guard.md)**<br>另一个活着的会话在同一工作树里时,拦截 `git checkout`、`stash`、`reset --hard`、`git add -A` 等命令,也拦截删除其他会话正在使用的文件夹。 |
118
- | **[wait-guard](docs/guards/wait-guard.md)**<br>拦截超过 3 分钟的前台 sleep 循环,这类循环会让下一次请求重写整个提示缓存。 | **[orphan-reaper](docs/guards/orphan-reaper.md)**<br>会话退出时,停掉它留下的开发服务器、测试 worker、监视进程和无头浏览器。 |
119
- | **[disk-guard](docs/guards/disk-guard.md)** <sup>new</sup><br>拦截 `du -sh ~`、不带 `-maxdepth` 的 `find /`,以及并行运行的磁盘扫描,它们会拖垮所有会话共用的磁盘。 | **[`bulkhead scan`](#需要它吗让你的会话记录来回答)** <sup>new</sup><br>用 guard 回放你过去的 Claude Code 会话记录:它们本来会拦下什么,以及有多少个会话同时在运行。 |
127
+ | **[wait-guard](docs/guards/wait-guard.md)**<br>拦截超过 3 分钟的前台 sleep 循环,这类循环会让下一次请求重写整个提示缓存。 | **[orphan-reaper](docs/guards/orphan-reaper.md)**<br>会话退出时,停掉它留下的开发服务器、测试 worker、监视进程和无头浏览器。需要保留的进程可以用 `BULKHEAD_KEEP=1` 特意留下;隧道和数据库默认就会保留。 |
128
+ | **[disk-guard](docs/guards/disk-guard.md)**<br>拦截 `du -sh ~`、不带 `-maxdepth` 的 `find /`,以及并行运行的磁盘扫描,它们会拖垮所有会话共用的磁盘。 | **[`bulkhead scan`](#需要它吗让你的会话记录来回答)**<br>用 guard 回放你过去的 Claude Code 会话记录:它们本来会拦下什么,以及有多少个会话同时在运行。 |
120
129
  | **[basics](docs/guards/basics.md)**<br>拦截对根目录、家目录或其下一级文件夹的 `rm -rf`、对 `main` 的强制推送、会丢掉未提交改动的 reset,以及以 Claude 作为作者的提交。 | **[`bulkhead status`、`top`、`log`、`doctor`](#查看正在运行的内容)**<br>显示每个会话的内存、端口、排队的任务,以及 guard 拦下了什么。 |
121
- | **[状态栏](#状态栏)**<br>在 Claude Code 状态栏显示 `3 sessions 2 heavy 9.1 GB free`,耗时不到 100 ms。 | **[配置与斜杠命令](#配置)**<br>机器级和项目级配置文件,支持放行列表。另有 `/bulkhead:scan`、`/bulkhead:status`、`/bulkhead:log`、`/bulkhead:doctor`、`/bulkhead:reap`。 |
130
+ | **[edit-guard](docs/guards/edit-guard.md)** <sup>new</sup><br>当另一个活着的会话在最近 30 分钟内编辑过同一个文件时(无论在这棵工作树还是仓库的另一个 worktree),提醒代理。默认只提示,不需要服务器。 | **[secrets-guard](docs/guards/secrets-guard.md)** <sup>new,默认关闭</sup><br>拦截通过 Read 工具或 shell 命令读取或打印 `.env`、私钥、云凭据和整个环境变量;`.env.example`、`ls ~/.ssh` 和 `.pub` 文件放行。 |
131
+ | **[`bulkhead explain` 与 `bulkhead allow`](#拦截了你本意要做的操作)** <sup>new</sup><br>在命令运行之前,查看每个 guard 会对它说什么;也可以用拦截提示里的代码,让一条被拦的命令放行一次。Claude 无法批准自己被拦下的操作。 | **[`bulkhead worktree`](#一步创建-worktree)** <sup>new</sup><br>一步创建新 worktree,自动复制你的 `.env` 文件、安装依赖,并分配固定不变的端口;`prune` 会删除已合并的 worktree(包括 squash 合并的),但绝不会删除有会话正在使用的。 |
132
+ | **[自动排队](docs/commands.md#auto-queue)** <sup>new</sup><br>开启 `memoryGuard.autoQueue` 后,重命令不再被拒绝,而是排队:hook 会用 `bulkhead-slot` 包装它们,有空间时再开始运行。 | **[Codex、Gemini 和其他代理](#codexgemini-和其他代理)** <sup>new</sup><br>为 Codex、Gemini CLI、Copilot CLI、Cursor、Windsurf、Droid、Devin、Cline、opencode 和 Amp 提供同一组 guard,用 `bulkhead setup <agent>` 配置。 |
133
+ | **[状态栏](#状态栏)**<br>在 Claude Code 状态栏显示 `3 sessions 2 heavy 9.1 GB free`,耗时约 100 ms。 | **[配置与斜杠命令](#配置)**<br>机器级和项目级配置文件,支持放行列表。另有 `/bulkhead:scan`、`/bulkhead:status`、`/bulkhead:log`、`/bulkhead:doctor`、`/bulkhead:reap`。 |
122
134
 
123
135
  可选功能:当本机内存不够时,**[`bulkhead remote`](#在另一台机器上运行重任务可选)** 通过 SSH 在另一台机器上运行测试套件、构建或浏览器测试。
124
136
 
@@ -132,14 +144,29 @@ claude-bulkhead 是一组小 hook,分别拦下或清理上面这些情况。
132
144
 
133
145
  1. 按上面的方式安装插件,然后新开一个 Claude Code 会话。
134
146
  2. 看看它本来会拦下什么:运行 `/bulkhead:scan 30d`,或在 Bash 工具里运行 `bulkhead scan`。它会读取你最近一个月的会话记录,不会改动任何东西。
135
- 3. 检查安装。在 Claude Code 里执行 `/bulkhead:doctor`,或在 Bash 工具里运行 `bulkhead doctor`。每一行都应该是 `OK`。`WARN` 或 `FAIL` 行会写明是哪个 guard、什么原因。
147
+ 3. 检查安装。在 Claude Code 里执行 `/bulkhead:doctor`,或在 Bash 工具里运行 `bulkhead doctor`。每一行都应该是 `OK`。`WARN` 或 `FAIL` 行会写明是哪个 guard、什么原因。最后的 Enforcement 部分会证明 hook 确实在拦截:它用一条测试命令运行已安装的 hook,检查退出码、提示信息和延迟,并标出 `disableAllHooks`、被禁用的插件或缺失的 `node`。
136
148
  4. 在会话旁边开一个终端,运行 `npx claude-bulkhead top`(执行 `npm install -g claude-bulkhead` 之后也可以用 `bulkhead top`),实时查看会话、内存、端口和重任务。按 `q` 退出。
137
149
 
138
150
  之后就不用再做什么。每次 Bash 和 PowerShell 调用都会经过 guard。想看拦下了什么,运行 `bulkhead log` 或 `/bulkhead:log`;`bulkhead log --summary` 按 guard 统计你这台机器上的拦截次数。要调整阈值或放行某条命令,见 [docs/configuration.md](docs/configuration.md)。全部命令见 [docs/commands.md](docs/commands.md)。
139
151
 
152
+ ## 一步创建 worktree
153
+
154
+ 另一个会话在你的工作树里工作时,checkout-guard 会让代理改用 worktree。但普通的 worktree 没有 `.env`,没有 `node_modules`,开发服务器端口也和主检出目录相同。`bulkhead worktree add` 一次解决这三件事:
155
+
156
+ ```
157
+ $ bulkhead worktree add fix-login
158
+ created ~/dev/shop-fix-login on new branch fix-login from HEAD
159
+ copied .env (default set)
160
+ pnpm install --frozen-lockfile: done in 14.2s
161
+ wrote PORT=3800 to .env.local
162
+ next: cd ~/dev/shop-fix-login && claude
163
+ ```
164
+
165
+ 它会复制 `.worktreeinclude`(Claude Code 也读取这个文件)里列出的文件,没有这个文件时复制一组安全的默认 `.env` 文件,绝不复制 `node_modules`、构建产物或任何超过 5 MB 的文件;按项目的 lockfile 安装依赖;并给这个 worktree 分配一个每次都相同的端口。`bulkhead worktree list` 会列出每个 worktree 的分支、状态、活着的会话和端口。`bulkhead worktree prune` 会列出已合并、干净且没人使用的 worktree,加上 `--yes` 后用 `git worktree remove` 和 `git branch -d` 删除,绝不使用 `--force`,也绝不会删除有活着的会话在用的文件夹。详情见 [docs/commands.md](docs/commands.md#bulkhead-worktree)。
166
+
140
167
  ## 在另一台机器上运行重任务(可选)
141
168
 
142
- 多个会话同时跑测试套件和构建时,一台机器的内存会不够用:memory-guard 会拦下命令,或者 bulkhead-slot 让它们排队。如果你还有一台基本空闲的机器,可以用 `bulkhead remote` 把重命令放到那台机器上运行。
169
+ 多个会话同时跑测试套件和构建时,一台机器的内存会不够用:memory-guard 会拦下命令,或者 bulkhead-slot 让它们排队(开启 `memoryGuard.autoQueue` 后会自动排队)。如果你还有一台基本空闲的机器,可以用 `bulkhead remote` 把重命令放到那台机器上运行。
143
170
 
144
171
  <p align="center">
145
172
  <img src="docs/remote.svg" alt="这台机器只剩 1.8 GB 空闲内存,memory-guard 拦下 npm test。bulkhead remote 只把新提交和改动的文件通过 SSH 发到 buildbox,测试在那边的副本 a、端口 7172 上运行,只传回最后 20 行。这台机器的空闲内存不变。" width="100%">
@@ -153,7 +180,7 @@ bulkhead remote run -- npm test
153
180
  ```
154
181
  [bulkhead remote] buildbox: 3f2a1c9 already there, 3 changed files (24 KB), 1 deleted
155
182
  [bulkhead remote] buildbox copy a /home/dev/bulkhead-remote/acme-web-3f9a1c port 7172
156
- ...测试输出...
183
+ ...test output...
157
184
  [bulkhead remote] exit 0 after 1m 12s (copy a)
158
185
  ```
159
186
 
@@ -163,7 +190,7 @@ bulkhead remote run -- npm test
163
190
  - **清理。** 命令结束或 SSH 连接断开(Ctrl-C、工具超时)时,它在远程启动的所有进程都会被停止,包括留在它端口上的服务器。
164
191
  - **远程机器** 只需要 SSH 服务器、`bash`、`git` 和 `tar`。Linux、macOS 以及装了 Git for Windows 的 Windows 都可以。
165
192
 
166
- `bulkhead remote fetch PATH` 从上一次运行用过的副本取回文件,`bulkhead remote status` 显示每台远程机器的空闲内存和正在运行的任务。在 Claude Code 里,`/bulkhead:remote <命令>` 会在后台把命令放到远程运行。配置、选项和退出码见 [docs/remote.md](docs/remote.md)(英文)。
193
+ `bulkhead remote fetch PATH` 从上一次运行用过的副本取回文件,`bulkhead remote status` 显示每台远程机器的空闲内存和正在运行的任务。在 Claude Code 里,`/bulkhead:remote <命令>` 会在后台把命令放到远程运行。配置、选项和退出码见 [docs/remote.md](docs/remote.md)。
167
194
 
168
195
  ## 工作原理
169
196
 
@@ -185,14 +212,14 @@ flowchart LR
185
212
 
186
213
  ## 各个 guard
187
214
 
188
- 每个 guard 都有单独的页面(英文),列出拦截的命令、放行的命令、Claude 看到的提示和已知限制。简要说明如下:
215
+ 每个 guard 都有单独的页面,列出拦截的命令、放行的命令、Claude 看到的提示和已知限制。简要说明如下:
189
216
 
190
217
  <details>
191
218
  <summary><strong>kill-guard</strong>:按名字结束进程</summary>
192
219
 
193
220
  拦截针对多个会话共用程序(如 `node`、`python`、`java`、`chrome`、`git`、`vite`)的 `taskkill /IM`、不带 PID 的 `taskkill /FI`、`Stop-Process -Name`、`Get-Process node | Stop-Process`、`pkill`、`killall`、`kill $(pgrep node)`、`wmic ... delete` 等命令。按 PID 或项目路径结束的命令放行。按端口结束的命令也放行,除非端口属于另一个会话,那种情况由 port-guard 检查。拦截时会告诉 Claude 如何找到自己的 PID。
194
221
 
195
- 关闭:`BULKHEAD_DISABLE=kill-guard`。详情:[docs/guards/kill-guard.md](docs/guards/kill-guard.md)
222
+ 关闭:`BULKHEAD_DISABLE=kill-guard`。详情:[docs/guards/kill-guard.md](docs/guards/kill-guard.md)。
196
223
  </details>
197
224
 
198
225
  <details>
@@ -200,7 +227,7 @@ flowchart LR
200
227
 
201
228
  如果另一个 Claude 会话的进程树已经在监听某个端口,在这个端口上启动开发服务器(`npm run dev`、`next dev`、`vite`、`uvicorn`、`rails s` 等)会被拦下,并给出一个空闲端口。另一个会话占用的端口,用 `npx kill-port 3000`、`fuser -k 3000/tcp`、`lsof -ti :3000 | xargs kill` 这类命令按端口结束,也会被拦下。
202
229
 
203
- 关闭:`BULKHEAD_DISABLE=port-guard`。详情:[docs/guards/port-guard.md](docs/guards/port-guard.md)
230
+ 关闭:`BULKHEAD_DISABLE=port-guard`。详情:[docs/guards/port-guard.md](docs/guards/port-guard.md)。
204
231
  </details>
205
232
 
206
233
  <details>
@@ -208,15 +235,17 @@ flowchart LR
208
235
 
209
236
  空闲内存低于 3 GB 时(内存小于 20 GB 的机器为总内存的 15%),memory-guard 拦截重命令(测试运行器、`next build`、`tsc`、Gradle、Android 模拟器、Playwright、`cargo test`、`docker build` 等)。`bulkhead-slot run --gb=4 -- <命令>` 会等到共享内存预算有空位,再运行命令,结束后释放位置。
210
237
 
211
- 关闭:`BULKHEAD_DISABLE=memory-guard`。详情:[docs/guards/memory-guard.md](docs/guards/memory-guard.md)
238
+ 关闭:`BULKHEAD_DISABLE=memory-guard`。详情:[docs/guards/memory-guard.md](docs/guards/memory-guard.md)。
239
+
240
+ 设置 `"memoryGuard": { "autoQueue": true }` 后,就不需要任何人记得使用 slot:memory-guard 会把每条重命令(测试、构建、浏览器运行、容器构建)改写成通过 `bulkhead-slot` 运行,所有会话在同一个公平的队列里等待,而不是被拦截。等待中的任务每 30 秒打印一次自己的排位,`bulkhead status` 会显示每个排队任务属于哪个会话和哪个项目。设置 `"memoryGuard": { "subagents": true }` 后,内存压力达到严重级别时不会再启动新的子代理。见 [docs/commands.md](docs/commands.md#auto-queue)。
212
241
  </details>
213
242
 
214
243
  <details>
215
244
  <summary><strong>checkout-guard</strong>:两个会话在同一个 git 工作树</summary>
216
245
 
217
- 另一个活着的会话在同一工作树里时,拦截 `git checkout <分支>`、`switch`、`stash`、`reset --hard`、`clean -f`、`rebase`、`merge`、`restore .` 以及有未提交改动时的 `pull`,并建议使用 `git worktree add`。在同样的情况下还会拦截 `git add -A`、`git add .` 和 `git commit -a`(`stage-all`),因为它们会把其他会话写到一半的改动卷进你的提交;也会拦截对其他活着的会话正在使用的文件夹执行 `rm -rf` 或 `git worktree remove`(`remove-session-dir`)。在 Windows 上,它还会拦截对含有 junction 的 worktree 执行 `git worktree remove --force`,因为 git 会顺着 junction 进入主检出目录。
246
+ 另一个活着的会话在同一工作树里时,拦截 `git checkout <分支>`、`switch`、`stash`、`reset --hard`、`clean -f`、`rebase`、`merge`、`restore .` 以及有未提交改动时的 `pull`,并建议使用 `git worktree add`。在同样的情况下还会拦截 `git add -A`、`git add .` 和 `git commit -a`(`stage-all`;`git add -A src/` 和 `git add -n` 放行),因为它们会把其他会话写到一半的改动卷进你的提交;也会拦截对其他活着的会话正在使用的文件夹执行 `rm -rf` 或 `git worktree remove`(`remove-session-dir`)。在 Windows 上,它还会拦截对含有 junction 的 worktree 执行 `git worktree remove --force`,因为 git 会顺着 junction 进入主检出目录。
218
247
 
219
- 关闭:`BULKHEAD_DISABLE=checkout-guard`,或用 `BULKHEAD_DISABLE=stage-all`、`remove-session-dir` 只关闭其中一条规则。详情:[docs/guards/checkout-guard.md](docs/guards/checkout-guard.md)
248
+ 关闭:`BULKHEAD_DISABLE=checkout-guard`,或用 `BULKHEAD_DISABLE=stage-all`、`remove-session-dir` 只关闭其中一条规则。详情:[docs/guards/checkout-guard.md](docs/guards/checkout-guard.md)。
220
249
  </details>
221
250
 
222
251
  <details>
@@ -224,7 +253,7 @@ flowchart LR
224
253
 
225
254
  拦截对整个驱动器、家目录或所有用户家目录的 `du`、`ncdu`、`dust`、`ls -R`、`Get-ChildItem -Recurse` 和 `dir /s`(`du -sh ~`、`du -sh ~/*`、`ncdu /`),不带 `-maxdepth` 的 `find /` 和 `find ~`,以及无论指向哪里的并行磁盘扫描:`xargs -P 10 du`、GNU `parallel`、循环里的 `du ... &`、`ForEach-Object -Parallel`。一次只看一个文件夹的命令放行(`du -sh node_modules`、`du -sh ~/Library/Caches`)。
226
255
 
227
- 关闭:`BULKHEAD_DISABLE=disk-guard`。详情:[docs/guards/disk-guard.md](docs/guards/disk-guard.md)
256
+ 关闭:`BULKHEAD_DISABLE=disk-guard`。详情:[docs/guards/disk-guard.md](docs/guards/disk-guard.md)。
228
257
  </details>
229
258
 
230
259
  <details>
@@ -232,25 +261,36 @@ flowchart LR
232
261
 
233
262
  拦截 sleep 加起来可能超过 180 秒的前台命令。大约 5 分钟没有请求,提示缓存就会过期,所以在前台轮询的子代理每次醒来都要把整个上下文重新写入缓存。拦截时会让 Claude 改用 `run_in_background: true`。后台任务里的 sleep(`sleep 600 &`)放行。工具超时为默认的 120 秒时,等待不可能超过上限,所以只有调用调高了超时才会拦截。
234
263
 
235
- 关闭:`BULKHEAD_DISABLE=wait-guard`,或用 `BULKHEAD_WAIT_MAX_SEC` 调高上限。详情:[docs/guards/wait-guard.md](docs/guards/wait-guard.md)
264
+ 关闭:`BULKHEAD_DISABLE=wait-guard`,或用 `BULKHEAD_WAIT_MAX_SEC` 调高上限。详情:[docs/guards/wait-guard.md](docs/guards/wait-guard.md)。
236
265
  </details>
237
266
 
238
267
  <details>
239
268
  <summary><strong>orphan-reaper</strong>:已退出会话留下的进程</summary>
240
269
 
241
- 会话退出时,一个隐藏的后台等待进程会停掉该会话从工具 shell 启动并遗留下来的进程:监听端口的开发服务器及其他进程、测试运行器及其 worker(Vitest、Jest、Playwright、pytest 和 xdist、`cargo test`、`go test` 等)、`tsc --watch` 和 nodemon 这类监视进程、`next build` 这类构建,以及无头浏览器。会话崩溃后遗留的进程,会在下一个会话启动时再扫一遍清理。编辑器、终端、Gradle 或 adb 这类共享守护进程,以及你自己启动的进程,都不会被碰。`bulkhead reap --dry-run` 可以先看它会停掉什么、为什么。支持 Windows、Linux 和 macOS(macOS 仅用测试数据验证)。
270
+ 会话退出时,一个隐藏的后台等待进程会停掉该会话从工具 shell 启动并遗留下来的进程:监听端口的开发服务器及其他进程、测试运行器及其 worker(Vitest、Jest、Playwright、pytest 和 xdist、`cargo test`、`go test` 等)、`tsc --watch` 和 nodemon 这类监视进程、`next build` 这类构建,以及无头浏览器。会话崩溃后遗留的进程,会在下一个会话启动时再扫一遍清理。编辑器、终端、Gradle 或 adb 这类共享守护进程,以及你自己启动的进程,都不会被碰。`bulkhead reap --dry-run` 可以先看它会停掉什么、为什么。你特意要让它一直运行的进程,用 `BULKHEAD_KEEP=1 <命令>` 启动即可保留(ssh 隧道、cloudflared、ngrok、Jupyter、Redis、Postgres、MySQL、MongoDB 和 Docker 不需要这个设置,默认就会保留),下一个会话会用一行话告诉你停掉了什么。支持 Windows、Linux 和 macOS(macOS 已在真机上测试)。
242
271
 
243
- 关闭:`BULKHEAD_DISABLE=orphan-reaper`。详情:[docs/guards/orphan-reaper.md](docs/guards/orphan-reaper.md)
272
+ 关闭:`BULKHEAD_DISABLE=orphan-reaper`。详情:[docs/guards/orphan-reaper.md](docs/guards/orphan-reaper.md)。
244
273
  </details>
245
274
 
246
275
  <details>
247
- <summary><strong>basics</strong>:四种无法撤销的操作</summary>
276
+ <summary><strong>basics</strong>:五种无法撤销的操作</summary>
248
277
 
249
- `rm-root`(对根目录、`/srv` 这样的顶级文件夹、家目录、`~/Documents` 这样家目录下一级的文件夹,或仓库上层目录的 `rm -rf`)、`force-push-main`(对 `main` 或 `master` 的 `git push --force`,`--force-with-lease` 放行)、`discard-changes`(会丢掉未提交改动的 `git reset --hard` 或 `git clean -fd`)和 `agent-author`(`--author="Claude <noreply@anthropic.com>"`、`git config user.name Claude` 等,让你的提交继续算作你自己的)。
278
+ `rm-root`(对根目录、`/srv` 这样的顶级文件夹、家目录、`~/Documents` 这样家目录下一级的文件夹,或仓库上层目录的 `rm -rf`)、`force-push-main`(对 `main` 或 `master` 的 `git push --force`,`--force-with-lease` 放行)、`protected-delete`(`git push origin :main`、`git push --delete origin main`、`git branch -D main`)、`discard-changes`(会丢掉未提交改动的 `git reset --hard` 或 `git clean -fd`)和 `agent-author`(`--author="Claude <noreply@anthropic.com>"`、`git config user.name Claude` 等,让你的提交继续算作你自己的)。
250
279
 
251
- 关闭:`BULKHEAD_DISABLE=basics`,也可以按规则名逐个关闭。详情:[docs/guards/basics.md](docs/guards/basics.md)
280
+ 关闭:`BULKHEAD_DISABLE=basics`,也可以按规则名逐个关闭。详情:[docs/guards/basics.md](docs/guards/basics.md)。
252
281
  </details>
253
282
 
283
+ ## Codex、Gemini 和其他代理
284
+
285
+ 如果你在运行 Claude Code 的同一台机器上还运行 Codex CLI、Gemini CLI、Copilot CLI、Cursor、opencode、Amp、Cline、Factory Droid、Windsurf 或 Devin,bulkhead 会用同样的 guard 保护它们的 shell 命令,并统计它们的会话:一个代理的开发服务器会受到保护,不被其他代理误伤,`bulkhead status` 也会多出一列 `AGENT`。
286
+
287
+ ```
288
+ bulkhead setup codex # prints the config for ~/.codex/hooks.json
289
+ bulkhead setup codex --write # merges it (original saved as hooks.json.bulkhead-backup)
290
+ ```
291
+
292
+ `bulkhead setup <agent>` 支持 `codex`、`gemini`、`copilot`、`cursor`、`windsurf`、`droid`、`devin`、`cline`、`opencode` 和 `amp`。每个代理在执行 shell 命令之前调用 `bulkhead hook --agent <name>`,并拿到属于自己的拦截格式。各个代理、配置文件、受保护的内容,以及每一行所依据的官方文档,见 [docs/other-agents.md](docs/other-agents.md)。
293
+
254
294
  ## 与其他 hook 集合的关系
255
295
 
256
296
  通用的安全 hook 集合保护单个会话不执行危险命令。例如 [claude-forge](https://github.com/sangrokjung/claude-forge) 带有过滤输出中泄露的密钥、拦截管道执行远程脚本和破坏性 SQL 的 hook;[dcg](https://github.com/Dicklesworthstone/destructive_command_guard) 和 [cc-safety-net](https://github.com/kenryu42/claude-code-safety-net) 广泛覆盖破坏性的 shell 和 git 命令。它们都值得使用。
@@ -283,7 +323,8 @@ bulkhead 解决的是另一个问题:同一台机器上的会话因为进程
283
323
 
284
324
  | 变量 | 默认值 | 对应配置 |
285
325
  |---|---|---|
286
- | `BULKHEAD_DISABLE` | 空 | 要关闭的 guard,用逗号分隔:`kill-guard`、`wait-guard`、`memory-guard`、`port-guard`、`checkout-guard`、`disk-guard`、`orphan-reaper`、`basics` 或 `all`。单条规则也可以单独关闭:`rm-root`、`force-push-main`、`discard-changes`、`agent-author`、`stage-all`、`remove-session-dir`。写在这里的 guard 无论文件怎么设置都会关闭 |
326
+ | `BULKHEAD_DISABLE` | 空 | 要关闭的 guard,用逗号分隔:`kill-guard`、`wait-guard`、`memory-guard`、`port-guard`、`checkout-guard`、`disk-guard`、`orphan-reaper`、`basics` 或 `all`。单条规则也可以单独关闭:`rm-root`、`force-push-main`、`protected-delete`、`discard-changes`、`agent-author`、`stage-all`、`remove-session-dir`。写在这里的 guard 无论文件怎么设置都会关闭 |
327
+ | `BULKHEAD_PRESET` | `normal` | 预设:`quiet`、`normal` 或 `strict` |
287
328
  | `BULKHEAD_WAIT_MAX_SEC` | 180 | `waitGuard.limitSeconds` |
288
329
  | `BULKHEAD_MIN_FREE_GB` | 3,或内存的 15%,取较小者 | `memory.minFreeGb` |
289
330
  | `BULKHEAD_SLOT_FLOOR_GB` | 5,或内存的 25%,取较小者 | `memory.slotFloorGb` |
@@ -294,32 +335,68 @@ bulkhead 解决的是另一个问题:同一台机器上的会话因为进程
294
335
 
295
336
  </details>
296
337
 
338
+ ## 拦截了你本意要做的操作
339
+
340
+ 拦截是一次询问,不是判决。有三种解决办法,从影响最小的开始:
341
+
342
+ 1. 放行一次。每条拦截提示的末尾都有一个短代码。在你自己的终端里运行 `bulkhead allow 7F3K`。代理接下来的同一条调用(同一条命令、同一个文件夹)会在 10 分钟内放行一次。代理无法替你做这件事:bulkhead 会拦截任何运行 `bulkhead allow` 或写入它状态文件的尝试。
343
+ 2. 先弄清原因。`bulkhead explain -- <命令>` 会针对当前的会话、端口和 git 工作树,让命令经过每个 guard,但不真正运行它,并打印挡路的东西归谁所有、代理看到的确切提示,以及如何放行。
344
+ 3. 永久放行,或调整拦截的松紧。把规则写进 `.claude/bulkhead.json`(见下表),或选择一个预设:`bulkhead config --preset quiet` 只保留跨会话的 guard 和两条无法撤销的规则,`strict` 以更严格的限制开启所有规则,`normal` 是默认值。你自己写的配置键和环境变量优先于预设。
345
+
346
+ ### 拦错了?速查表
347
+
348
+ 一次性的办法永远是由你运行 `bulkhead allow <code>`。针对触发的规则,永久的办法如下:
349
+
350
+ | Guard | 规则 | 永久放行,写在 `.claude/bulkhead.json` |
351
+ |---|---|---|
352
+ | kill-guard | `pkill`、`taskkill-im`、`stop-process-name` 以及其他按名字结束进程的规则 | `{"killGuard":{"allowNames":["ffmpeg"]}}` |
353
+ | kill-guard | `other-session-pid`: 属于其他会话进程树的 PID | `{"killGuard":{"ownerCheck":false}}` |
354
+ | kill-guard | `mass-kill`, `docker-mass`, `device-kill`, `app-quit`, `service-stop`: 其他规则(仅当有其他会话在运行时) | `{"killGuard":{"massKill":false}}`, 其余同理 (`dockerMass`, `deviceKill`, `appQuit`, `serviceStop`) |
355
+ | wait-guard | `long-sleep`、`poll-loop`、`follow` | `{"waitGuard":{"limitSeconds":600}}` |
356
+ | memory-guard | `low-memory` | `{"memory":{"minFreeGb":1}}` |
357
+ | memory-guard | `slot-files` | `{"memoryGuard":{"enabled":false}}` |
358
+ | port-guard | `port-in-use`、`kill-by-port` | `{"portGuard":{"ignorePorts":[3000]}}` |
359
+ | checkout-guard | `stage-all` | `{"checkoutGuard":{"stageAll":false}}` |
360
+ | checkout-guard | `remove-session-dir` | `{"checkoutGuard":{"removeSessionDir":false}}` |
361
+ | checkout-guard | `git-checkout`、`git-pull` 以及其他改动工作树的规则 | `{"checkoutGuard":{"enabled":false}}` |
362
+ | disk-guard | `disk-wide-find`、`parallel-disk-scan`、`disk-wide-scan` | `{"diskGuard":{"enabled":false}}` |
363
+ | basics | `rm-root` | `{"basics":{"rmRoot":false}}` |
364
+ | basics | `force-push-main` | `{"basics":{"forcePushMain":false}}` |
365
+ | basics | `discard-changes` | `{"basics":{"discardChanges":false}}` |
366
+ | basics | `agent-author` | `{"basics":{"agentAuthor":false}}` |
367
+ | basics | `self-allow` | 不能放行一次;只有你自己才能批准 |
368
+
369
+ 用环境变量实现同样的开关是 `BULKHEAD_DISABLE=<guard 或规则名>`。完整说明见 [docs/configuration.md](docs/configuration.md) 和 [docs/commands.md](docs/commands.md)。
370
+
297
371
  ## 查看正在运行的内容
298
372
 
299
373
  `bulkhead` 和 `bulkhead-slot` 一起位于 Bash 工具的 `PATH` 上。在 Claude Code 里,`/bulkhead:scan [30d]`、`/bulkhead:status`、`/bulkhead:log [24h|7d]`、`/bulkhead:doctor`、`/bulkhead:reap` 会运行对应命令并总结结果。在自己的终端里,运行 `npx claude-bulkhead top`,或者先用 `npm install -g claude-bulkhead` 安装一次,再运行 `bulkhead top`。npm 包与插件是同一份代码。所有输出表格的命令都支持 `--json`。完整说明见 [docs/commands.md](docs/commands.md)。
300
374
 
301
375
  ### bulkhead status
302
376
 
303
- 列出机器上所有 Claude Code 会话、各自进程树占用的内存、监听的端口、bulkhead-slot 持有和等待中的任务,以及空闲内存:
377
+ 列出机器上所有 Claude Code 会话:它正在做什么(工作中、等你、需要授权)、git 分支以及是否是 worktree、进程树占用的内存、监听的端口、bulkhead-slot 持有和等待中的任务、空闲内存,以及在多个会话里各跑一份的 MCP 服务器:
304
378
 
305
379
  ```
306
380
  $ bulkhead status
307
- SESSION STARTED RSS PROJECT
308
- 18672 Oct 12 17:49 1.4 GB ~/src/acme-web
309
- :3000 node.exe pid 24632
310
- 21888 Oct 12 17:52 353 MB ~/src/acme-web-auth
311
- 26448 03:43 625 MB ~/src/acme-web-checkout
312
- :3001 node.exe pid 31020
381
+ SESSION STATE STARTED RSS BRANCH PROJECT
382
+ 18672 working 09:52 1.4 GB main ~/src/acme-web
383
+ :3000 node pid 24632
384
+ 21888 needs permission 4m 10:05 353 MB feat/login (wt) ~/src/acme-web-auth
385
+ 26448 waiting 3m 11:40 625 MB feat/checkout (wt) ~/src/acme-web-checkout
386
+ :3001 node pid 31020
387
+ duplicate MCP: 3 copies of @playwright/mcp, 1.2 GB (sessions 18672, 21888, 26448)
313
388
 
314
- SLOT GB PID AGE COMMAND
315
- slot-1 4 30112 6 min npx playwright test
389
+ SLOT GB PID AGE SESSION PROJECT COMMAND
390
+ slot-1 4 30112 6 min 18672 ~/src/acme-web npx playwright test
316
391
 
317
- WAITING GB PID FOR COMMAND
318
- #1 3 30200 2 min npm run build
392
+ WAITING GB PID FOR SESSION PROJECT COMMAND
393
+ #1 3 30200 2 min 26448 ~/src/acme-web-checkout npm run build
319
394
 
320
395
  9.9 GB free of 31.8 GB, commit 68%
321
396
  ```
322
397
 
398
+ `bulkhead top` 会把需要你处理的会话排在最前面。`bulkhead port --stable` 会输出一个属于当前 git worktree、每次都相同的端口。想在会话开始等待时收到桌面通知,设置 `notify.command`([commands.md](docs/commands.md#session-state-and-notifications))。
399
+
323
400
  `bulkhead slot ...` 等同于 `bulkhead-slot ...`。
324
401
 
325
402
  ### bulkhead top
@@ -374,31 +451,44 @@ PROJECT COUNT
374
451
 
375
452
  ### bulkhead doctor
376
453
 
377
- 检查 Node、插件文件、状态目录、配置文件、每个 guard 是否开启及原因、一次真实的 hook 运行、进程读取及耗时、会话文件、按本机计算的内存阈值、已持有的 slot、事件日志和 git。任何一项失败时退出码为 1。
454
+ 检查 Node、插件文件、状态目录、配置文件、每个 guard 是否开启及原因、进程读取及耗时、会话文件、按本机计算的内存阈值、已持有的 slot、事件日志和 git。随后的 Enforcement 部分会证明已安装的 hook 确实在拦截:安装情况和 `enabledPlugins`、重复安装、关闭 hook 的设置、hook `PATH` 上的 `node`,以及用一条测试命令真实运行每个 guard 的 hook 命令并给出延迟。任何一项失败时退出码为 1。
378
455
 
379
456
  ```
380
457
  $ bulkhead doctor
381
- OK node v24.11.0
382
- OK plugin ~/.claude/plugins/cache/bulkhead/bulkhead/0.2.2, hooks: 2 SessionStart, 1 SessionEnd, 7 PreToolUse
383
- OK state dir ~/.claude/bulkhead is writable
384
- OK config no config files, defaults in use
385
- OK kill-guard on
386
- OK wait-guard on, limit 180s
387
- OK memory-guard on
388
- OK port-guard on
389
- OK checkout-guard on
390
- OK disk-guard on
391
- OK basics on, protected branches: main, master
392
- OK orphan-reaper on, at session start and after each session ends
393
- OK hook run kill-guard blocked a test command in 90 ms
394
- OK processes 356 processes, 21 listening ports, 8 Claude sessions in 2.4s
395
- OK sessions ~/.claude/sessions (8 session files)
396
- OK memory 12.2 of 31.8 GB free, commit 43%, memory-guard blocks heavy commands below 3 GB, bulkhead-slot keeps 5 GB free for a second job, and waits while commit is above 75%
397
- OK slots none held
398
- OK event log ~/.claude/bulkhead/events.jsonl, empty so far
399
- OK git 2.52.0.windows.1
400
-
401
- 19 OK, 0 WARN, 0 FAIL
458
+ OK node v24.22.1
459
+ OK package this copy ~/.claude/plugins/cache/bulkhead/bulkhead/0.3.0, hooks: 3 SessionStart, 2 SessionEnd, 1 UserPromptSubmit, 1 Stop, 1 Notification, 2 PostToolUse, 1 PostToolUseFailure, 4 PreToolUse
460
+ OK state dir ~/.claude/bulkhead is writable
461
+ OK config no config files, defaults in use
462
+ OK preset normal (default)
463
+ OK kill-guard on
464
+ OK wait-guard on, limit 180s
465
+ OK memory-guard on
466
+ OK port-guard on
467
+ OK checkout-guard on
468
+ OK disk-guard on
469
+ OK basics on, protected branches: main, master
470
+ OK edit-guard on
471
+ OK secrets-guard off (default)
472
+ OK orphan-reaper on, at session start and after each session ends
473
+ OK session-state on
474
+ OK processes 589 processes, 11 listening ports, 10 Claude sessions (10 with a session file) in 0.2s
475
+ OK sessions ~/.claude/sessions (10 session files; a running session without one is still found from the process list, but checkout-guard cannot tell which folder it works in)
476
+ OK memory 28.3 of 48.0 GB free, memory-guard blocks heavy commands below 3 GB, bulkhead-slot keeps 5 GB free for a second job
477
+ OK slots none held
478
+ OK event log ~/.claude/bulkhead/events.jsonl, 2 events, 254 B (rotates at 1 MB)
479
+ OK git 2.56.0
480
+
481
+ Enforcement
482
+ OK install bulkhead@bulkhead 0.3.0 at ~/.claude/plugins/cache/bulkhead/bulkhead/0.3.0, enabled in user settings
483
+ OK hooks on no disableAllHooks or allowManagedHooksOnly in user, project, local or managed settings
484
+ OK hooks.json installed copy: 4 PreToolUse hooks, all synchronous
485
+ OK hook node /opt/homebrew/bin/node v24.22.1
486
+ OK hook kill-guard blocked "pkill node" (exit 2, message shown to Claude) in 71 ms
487
+ OK hook wait-guard blocked "sleep 100000" (exit 2, message shown to Claude) in 64 ms
488
+ OK hook disk-guard blocked "du -sh /" (exit 2, message shown to Claude) in 70 ms
489
+ OK hook basics blocked "rm -rf /" (exit 2, message shown to Claude) in 63 ms
490
+
491
+ 30 OK, 0 WARN, 0 FAIL
402
492
  ```
403
493
 
404
494
  ### bulkhead port
@@ -428,7 +518,7 @@ would stop 30112 test-runner node ~/src/acme-web/node_modules/vitest/dist/work
428
518
  3 sessions 2 heavy 9.1 GB free
429
519
  ```
430
520
 
431
- sessions 是 `~/.claude/sessions` 里活着的会话数,heavy 是持有 bulkhead-slot 的任务数,free 与 memory-guard 使用的空闲内存口径相同。它不扫描进程,耗时不到 100 ms,并在状态目录中缓存 5 秒。
521
+ sessions 是 `~/.claude/sessions` 里活着的会话数,heavy 是持有 bulkhead-slot 的任务数,free 与 memory-guard 使用的空闲内存口径相同。有会话在等你时,这一行会在会话数后面加上 `2 waiting`。它不会列出所有进程(只对会话 PID 执行一次 `ps`),在一台有 650 个进程的 Mac 上冷启动约需 100 ms,并在状态目录中缓存 5 秒;缓存先写入临时文件再替换,所以并发的会话不会读到写了一半的内容。
432
522
 
433
523
  插件无法设置主状态栏(插件设置只支持 `agent` 和 `subagentStatusLine`),需要自己加到 `~/.claude/settings.json`:
434
524
 
@@ -447,7 +537,7 @@ sessions 是 `~/.claude/sessions` 里活着的会话数,heavy 是持有 bulkhe
447
537
  ## 常见问题
448
538
 
449
539
  **会拖慢 Claude Code 吗?**
450
- 会慢一点。每个 guard 是一个很短的 Node 进程。在一台 Windows 11、Node 24 的台式机上,`node scripts/bench-hooks.js 15` 测得每个 guard 处理一条放行命令耗时 40 到 49 ms(15 次运行的中位数),单独启动 Node 就要 33 ms。把拦截写入事件日志的开销测不出来。读取进程表(Windows 上需要一两秒)这类慢操作只在命令匹配时才做:在已有响应的端口上启动开发服务器、结束进程、运行重命令。状态栏耗时不到 100 ms,并缓存 5 秒。
540
+ 会慢一点。所有 Bash guard 在同一个没有依赖的短命 Node 进程里运行,在 MacBook Pro(Node 24)上每次工具调用约 76 ms,其中 32 ms 是启动 Node 的时间。10 个会话在同一时刻发起工具调用时,每个约等待 89 ms(每个 guard 各占一个进程时是 318 ms,0.2 版就是这样)。把拦截写入事件日志的开销测不出来。读取进程列表(Windows 上需要一两秒)这类慢操作只在命令匹配时才做:在已有响应的端口上启动开发服务器、结束进程、运行重命令。状态栏冷启动约 100 ms,并缓存 5 秒。
451
541
 
452
542
  **它拦了不该拦的命令,怎么办?**
453
543
  用 `bulkhead log` 查看具体命令和原因。在项目配置 `<repo>/.claude/bulkhead.json` 里放行:程序名用 `killGuard.allowNames`,共用端口用 `portGuard.ignorePorts`,更长的等待用 `waitGuard.limitSeconds`,要在该项目关闭整个 guard 就写 `"enabled": false`。见 [docs/configuration.md](docs/configuration.md)。然后请附上 `bulkhead log` 里的那一行提交一个 [误报 issue](https://github.com/dooly6276/claude-bulkhead/issues/new?template=false-positive.yml),方便修正规则。
@@ -456,12 +546,12 @@ sessions 是 `~/.claude/sessions` 里活着的会话数,heavy 是持有 bulkhe
456
546
  有用。kill-guard、wait-guard、memory-guard、basics 对单个会话及其子代理同样有效。port-guard 和 checkout-guard 只在有其他会话存活时才起作用。orphan-reaper 会清理已退出会话留下的进程。
457
547
 
458
548
  **支持哪些系统?**
459
- Windows、Linux 和 macOS,需要 Node 18 或更高版本。orphan-reaper 在 macOS 上只用 `ps` 输出的测试数据验证过,尚未在真机上运行;在 Linux 上需要会给工具 shell 设置 `CLAUDE_PID` 的 Claude Code 版本。在 Windows 上,`bulkhead-slot` 优先通过 Git Bash 运行命令,没有 Git Bash 时使用 `cmd.exe`。
549
+ Windows、Linux 和 macOS,需要 Node 18 或更高版本。orphan-reaper 在 macOS 上既用 `ps` 输出的测试数据验证过,也在真机上测试过(结束一个替身会话,只停掉它遗留的进程);在 Linux 上需要会给工具 shell 设置 `CLAUDE_PID` 的 Claude Code 版本。在 Windows 上,`bulkhead-slot` 优先通过 Git Bash 运行命令,没有 Git Bash 时使用 `cmd.exe`。
460
550
 
461
551
  **会向外发送数据吗?**
462
552
  不会。没有遥测,也没有网络请求。唯一打开的套接字是 port-guard 连接 `localhost` 来判断某个端口是否有响应。日志只保存在状态目录里。唯一的例外是 `bulkhead remote`:只有在你配置了远程机器并主动运行它时,才会通过 SSH 连接那台机器。
463
553
 
464
- 更多问题(例如能否绕过 guard、内存数值如何测量)见 [docs/faq.md](docs/faq.md)(英文)。
554
+ 更多问题(例如能否绕过 guard、内存数值如何测量)见 [docs/faq.md](docs/faq.md)。
465
555
 
466
556
  ## 限制
467
557
 
@@ -477,6 +567,8 @@ Windows、Linux 和 macOS,需要 Node 18 或更高版本。orphan-reaper 在 m
477
567
  /plugin marketplace remove bulkhead
478
568
  ```
479
569
 
570
+ 如果你安装的是 npm 包,运行 `npm uninstall -g claude-bulkhead`。已经在运行的 SessionEnd 等待进程会在 3 分钟内自行退出,所以卸载之后不会有东西继续运行。
571
+
480
572
  然后删除状态目录(`~/.claude/bulkhead`,或你设置的 `BULKHEAD_STATE_DIR`),如果添加过状态栏,再从 `~/.claude/settings.json` 中删掉 `statusLine` 项。
481
573
 
482
574
  ## 在用 bulkhead?
package/hooks/hooks.json CHANGED
@@ -7,6 +7,11 @@
7
7
  { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/orphan-reaper.js"], "async": true }
8
8
  ]
9
9
  },
10
+ {
11
+ "hooks": [
12
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "timeout": 5 }
13
+ ]
14
+ },
10
15
  {
11
16
  "matcher": "startup",
12
17
  "hooks": [
@@ -17,7 +22,50 @@
17
22
  "SessionEnd": [
18
23
  {
19
24
  "hooks": [
20
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/orphan-reaper.js", "--session-end"] }
25
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/orphan-reaper.js", "--session-end"] },
26
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"] }
27
+ ]
28
+ }
29
+ ],
30
+ "UserPromptSubmit": [
31
+ {
32
+ "hooks": [
33
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "timeout": 5 }
34
+ ]
35
+ }
36
+ ],
37
+ "Stop": [
38
+ {
39
+ "hooks": [
40
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "timeout": 5 }
41
+ ]
42
+ }
43
+ ],
44
+ "Notification": [
45
+ {
46
+ "matcher": "permission_prompt|idle_prompt|elicitation_dialog|elicitation_url_dialog",
47
+ "hooks": [
48
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "timeout": 5 }
49
+ ]
50
+ }
51
+ ],
52
+ "PostToolUse": [
53
+ {
54
+ "hooks": [
55
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "async": true, "timeout": 5 }
56
+ ]
57
+ },
58
+ {
59
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
60
+ "hooks": [
61
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/edit-guard.js"], "timeout": 10 }
62
+ ]
63
+ }
64
+ ],
65
+ "PostToolUseFailure": [
66
+ {
67
+ "hooks": [
68
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/session-state.js"], "async": true, "timeout": 5 }
21
69
  ]
22
70
  }
23
71
  ],
@@ -25,13 +73,25 @@
25
73
  {
26
74
  "matcher": "Bash|PowerShell",
27
75
  "hooks": [
28
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/kill-guard.js"], "timeout": 10 },
29
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/wait-guard.js"], "timeout": 10 },
30
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/memory-guard.js"], "timeout": 15 },
31
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/port-guard.js"], "timeout": 20 },
32
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/checkout-guard.js"], "timeout": 15 },
33
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/disk-guard.js"], "timeout": 10 },
34
- { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/basics.js"], "timeout": 10 }
76
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/bash-guards.js"], "timeout": 30 }
77
+ ]
78
+ },
79
+ {
80
+ "matcher": "Agent|Task",
81
+ "hooks": [
82
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/subagent-guard.js"], "timeout": 10 }
83
+ ]
84
+ },
85
+ {
86
+ "matcher": "Read|Grep",
87
+ "hooks": [
88
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/secrets-guard.js"], "timeout": 10 }
89
+ ]
90
+ },
91
+ {
92
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
93
+ "hooks": [
94
+ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/src/edit-guard.js"], "timeout": 10 }
35
95
  ]
36
96
  }
37
97
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-bulkhead",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Code hooks for a machine shared by many sessions and subagents",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -17,7 +17,10 @@
17
17
  ],
18
18
  "license": "MIT",
19
19
  "author": "Myeongseok Kang",
20
- "repository": "github:dooly6276/claude-bulkhead",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/dooly6276/claude-bulkhead.git"
23
+ },
21
24
  "homepage": "https://github.com/dooly6276/claude-bulkhead#readme",
22
25
  "bugs": {
23
26
  "url": "https://github.com/dooly6276/claude-bulkhead/issues"