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.md CHANGED
@@ -4,7 +4,9 @@
4
4
  <strong>Stop Claude Code from running <code>pkill node</code> on your other dev servers, taking their ports, and committing another session's edits with <code>git add -A</code>.</strong>
5
5
  </p>
6
6
 
7
- <p align="center">Hooks that know which processes, ports and git trees belong to which Claude Code session and subagent, so they block only what would hurt something else, and tell Claude the safe command to run instead. Zero dependencies, no telemetry.</p>
7
+ <p align="center">Hooks that know which processes, ports and git trees belong to which Claude Code session and subagent, so they block only what would hurt something else, and tell Claude the safe command to run instead.</p>
8
+
9
+ <p align="center">Windows, macOS and Linux. No tmux, no sudo, no background daemon, no network calls, no dependencies. Also guards Codex, Gemini CLI, Copilot CLI, Cursor and six more agents.</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 @@ Then see what it would have stopped in your own last month, without changing any
34
36
  /bulkhead:scan 30d
35
37
  ```
36
38
 
37
- Node 18 or later must be on `PATH` (the native Claude Code installer does not install it; check with `node --version`). For the terminal commands and the status line, also run `npm install -g claude-bulkhead`, or use `npx claude-bulkhead status` without installing.
39
+ Node 18 or later must be on `PATH` (the native Claude Code installer does not install it; check with `node --version`).
40
+
41
+ | Install | What you get |
42
+ |---|---|
43
+ | The plugin (above) | The guards, which run on every Bash, PowerShell, Read and Edit call; the slash commands `/bulkhead:scan`, `status`, `log`, `doctor`, `reap`, `remote`; the skill that teaches Claude the safe habits. This is the part that protects you. |
44
+ | `npm install -g claude-bulkhead` (optional) | The `bulkhead` command in your own terminal (`status`, `top`, `explain`, `allow`, `worktree`, `scan`, `doctor`), the status line, `bulkhead-slot`, and `bulkhead setup <agent>` for Codex, Gemini and the others. `npx claude-bulkhead status` works without installing. |
45
+
46
+ Unsure whether it is working? `bulkhead doctor` runs a harmless command through each guard and shows that it was blocked, and how long it took.
38
47
 
39
48
  ## What it stops
40
49
 
@@ -87,7 +96,7 @@ You do not have to guess. Claude Code keeps every session's transcript in `~/.cl
87
96
  /bulkhead:scan 30d
88
97
  ```
89
98
 
90
- It only reads, masks anything that looks like a secret before printing, and sends nothing anywhere. port-guard and the low-RAM check are not replayed, because transcripts do not record which ports and how much RAM were in use; checkout-guard is rebuilt from which sessions were active in the same git tree at that minute. Details: [docs/commands.md](docs/commands.md#bulkhead-scan).
99
+ It only reads, masks anything that looks like a secret before printing, and sends nothing anywhere. port-guard and the low-RAM check are not replayed, because transcripts do not record which ports and how much RAM were in use; checkout-guard is rebuilt from which sessions were active in the same git tree at that minute. `bulkhead scan --share` prints a 5 to 8 line summary with counts only (no paths, no commands) that you can paste into a post or a chat, and `--explain 1` shows one example in full with the part that matched. Details: [docs/commands.md](docs/commands.md#bulkhead-scan).
91
100
 
92
101
  ## Why this exists
93
102
 
@@ -113,12 +122,15 @@ claude-bulkhead is a set of small hooks that block or clean up each of these. Ea
113
122
 
114
123
  | | |
115
124
  |---|---|
116
- | **[kill-guard](docs/guards/kill-guard.md)**<br>Blocks `pkill node`, `taskkill /IM`, `Stop-Process -Name` and other kills by program name. Kills by PID pass. | **[port-guard](docs/guards/port-guard.md)**<br>Blocks starting a dev server on a port another session already serves, and killing that session's server by port. |
125
+ | **[kill-guard](docs/guards/kill-guard.md)**<br>Blocks `pkill node`, `taskkill /IM`, `Stop-Process -Name` and other kills by program name, and mass kills (`docker system prune`, `pkill -u`, `adb kill-server`) while another session is alive. Kills of your own processes by PID pass; `kill <pid>` of another session's process does not. | **[port-guard](docs/guards/port-guard.md)**<br>Blocks starting a dev server on a port another session already serves, and killing that session's server by port. |
117
126
  | **[memory-guard and bulkhead-slot](docs/guards/memory-guard.md)**<br>Blocks heavy commands when RAM is low, and queues heavy jobs against one RAM budget shared by every session. | **[checkout-guard](docs/guards/checkout-guard.md)**<br>Blocks `git checkout`, `stash`, `reset --hard`, `git add -A` and similar when another live session works in the same tree, and deleting a folder another session works in. |
118
- | **[wait-guard](docs/guards/wait-guard.md)**<br>Blocks foreground sleep loops longer than 3 minutes, which make the next request rewrite the whole prompt cache. | **[orphan-reaper](docs/guards/orphan-reaper.md)**<br>When a session exits, stops the dev servers, test workers, watchers and headless browsers it left running. |
119
- | **[disk-guard](docs/guards/disk-guard.md)** <sup>new</sup><br>Blocks `du -sh ~`, `find /` without `-maxdepth`, and disk scans run in parallel, which stall the disk every session shares. | **[`bulkhead scan`](#do-you-need-it-ask-your-transcripts)** <sup>new</sup><br>Replays your past Claude Code transcripts against the guards: what they would have stopped, and how many sessions ran at once. |
127
+ | **[wait-guard](docs/guards/wait-guard.md)**<br>Blocks foreground sleep loops longer than 3 minutes, which make the next request rewrite the whole prompt cache. | **[orphan-reaper](docs/guards/orphan-reaper.md)**<br>When a session exits, stops the dev servers, test workers, watchers and headless browsers it left running. Keep one on purpose with `BULKHEAD_KEEP=1`; tunnels and databases are kept already. |
128
+ | **[disk-guard](docs/guards/disk-guard.md)**<br>Blocks `du -sh ~`, `find /` without `-maxdepth`, and disk scans run in parallel, which stall the disk every session shares. | **[`bulkhead scan`](#do-you-need-it-ask-your-transcripts)**<br>Replays your past Claude Code transcripts against the guards: what they would have stopped, and how many sessions ran at once. |
120
129
  | **[basics](docs/guards/basics.md)**<br>Blocks `rm -rf` at a root, home or a folder directly in it, force-pushes to `main`, resets that would lose uncommitted work, and commits authored as Claude. | **[`bulkhead status`, `top`, `log`, `doctor`](#see-what-is-running)**<br>Shows every session, its RAM, its ports, queued jobs, and what the guards have blocked. |
121
- | **[Status line](#status-line)**<br>`3 sessions 2 heavy 9.1 GB free` in the Claude Code status line, in under 100 ms. | **[Config and slash commands](#configuration)**<br>Per-machine and per-project config with allow lists, plus `/bulkhead:scan`, `/bulkhead:status`, `/bulkhead:log`, `/bulkhead:doctor` and `/bulkhead:reap`. |
130
+ | **[edit-guard](docs/guards/edit-guard.md)** <sup>new</sup><br>Tells the agent when another live session edited the same file in the last 30 minutes, in this tree or another worktree of the repository. Advisory by default, no server. | **[secrets-guard](docs/guards/secrets-guard.md)** <sup>new, off by default</sup><br>Blocks reading or printing `.env`, private keys, cloud credentials and the whole environment, by the Read tool or a shell command, while `.env.example`, `ls ~/.ssh` and `.pub` files pass. |
131
+ | **[`bulkhead explain` and `bulkhead allow`](#when-it-blocks-something-you-meant)** <sup>new</sup><br>See what every guard would say about a command before it runs, and let one blocked command through once with the code in the block message. Claude cannot approve its own blocks. | **[`bulkhead worktree`](#worktrees-in-one-step)** <sup>new</sup><br>A new worktree in one step, with your `.env` files copied, dependencies installed and its own stable port; `prune` removes merged ones, squash merges included, and never one a session works in. |
132
+ | **[Auto-queue](docs/commands.md#auto-queue)** <sup>new</sup><br>With `memoryGuard.autoQueue`, heavy commands are not refused but put in line: the hook wraps them in `bulkhead-slot`, and they start when there is room. | **[Codex, Gemini and other agents](#codex-gemini-and-other-agents)** <sup>new</sup><br>The same guards for Codex, Gemini CLI, Copilot CLI, Cursor, Windsurf, Droid, Devin, Cline, opencode and Amp, set up with `bulkhead setup <agent>`. |
133
+ | **[Status line](#status-line)**<br>`3 sessions 2 heavy 9.1 GB free` in the Claude Code status line, in about 100 ms. | **[Config and slash commands](#configuration)**<br>Per-machine and per-project config with allow lists, plus `/bulkhead:scan`, `/bulkhead:status`, `/bulkhead:log`, `/bulkhead:doctor` and `/bulkhead:reap`. |
122
134
 
123
135
  Optional: **[`bulkhead remote`](#run-heavy-jobs-on-another-machine-optional)** runs a test suite, a build or a browser run on another machine over SSH, when this one has no RAM to spare.
124
136
 
@@ -132,14 +144,29 @@ A scripted run of `bulkhead status`, port-guard, kill-guard and wait-guard, reco
132
144
 
133
145
  1. Install the plugin as above and start a new Claude Code session.
134
146
  2. See what it would have caught: run `/bulkhead:scan 30d`, or `bulkhead scan` in the Bash tool. It reads your last month of transcripts and changes nothing.
135
- 3. Check the setup. Inside Claude Code run `/bulkhead:doctor`, or run `bulkhead doctor` in the Bash tool. Every line should say `OK`. A `WARN` or `FAIL` line names the guard and the reason.
147
+ 3. Check the setup. Inside Claude Code run `/bulkhead:doctor`, or run `bulkhead doctor` in the Bash tool. Every line should say `OK`. A `WARN` or `FAIL` line names the guard and the reason. The Enforcement section at the end proves the hooks block: it runs the installed hook with a canary command and checks the exit code, the message and the latency, and it flags `disableAllHooks`, a disabled plugin or a missing `node`.
136
148
  4. Open a terminal next to your sessions and run `npx claude-bulkhead top` (or `bulkhead top` after `npm install -g claude-bulkhead`) to watch sessions, RAM, ports and heavy jobs live. Press `q` to quit.
137
149
 
138
150
  After that there is nothing to do. The guards run on every Bash and PowerShell call. To see what they stopped, run `bulkhead log` or `/bulkhead:log`; `bulkhead log --summary` counts blocks per guard on your machine. To change a threshold or allow a command, see [docs/configuration.md](docs/configuration.md). All commands are listed in [docs/commands.md](docs/commands.md).
139
151
 
152
+ ## Worktrees in one step
153
+
154
+ When another session works in your tree, checkout-guard sends the agent to a worktree. A plain worktree has no `.env`, no `node_modules` and the same dev server port as the main checkout. `bulkhead worktree add` fixes those three:
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
+ It copies the files named in `.worktreeinclude` (the file Claude Code reads too) or a safe default set of `.env` files, never `node_modules`, build output or anything over 5 MB; installs with the project's lockfile; and gives the worktree a port that stays the same every time. `bulkhead worktree list` shows each worktree with its branch, state, live session and port. `bulkhead worktree prune` lists the ones that are merged, clean and unused, and removes them with `--yes` using `git worktree remove` and `git branch -d`, never `--force`, never a live session's folder. Details in [docs/commands.md](docs/commands.md#bulkhead-worktree).
166
+
140
167
  ## Run heavy jobs on another machine (optional)
141
168
 
142
- When several sessions run test suites and builds at once, one machine runs out of RAM: memory-guard blocks the commands, or bulkhead-slot makes them queue. If you have a second machine that is mostly idle, `bulkhead remote` runs the heavy command there instead.
169
+ When several sessions run test suites and builds at once, one machine runs out of RAM: memory-guard blocks the commands, or bulkhead-slot makes them queue (automatically, with `memoryGuard.autoQueue`). If you have a second machine that is mostly idle, `bulkhead remote` runs the heavy command there instead.
143
170
 
144
171
  <p align="center">
145
172
  <img src="docs/remote.svg" alt="This machine has 1.8 GB free, so memory-guard holds npm test. bulkhead remote sends only the new commit and the changed files over SSH to buildbox, the tests run there in copy a on port 7172, and the last 20 lines come back. Free RAM on this machine does not change." width="100%">
@@ -209,12 +236,14 @@ Turn off: `BULKHEAD_DISABLE=port-guard`. Details: [docs/guards/port-guard.md](do
209
236
  memory-guard blocks heavy commands (test runners, `next build`, `tsc`, Gradle, the Android emulator, Playwright, `cargo test`, `docker build` and similar) when free RAM is under 3 GB, or under 15% of RAM on machines with less than 20 GB. `bulkhead-slot run --gb=4 -- <command>` waits until the shared RAM budget has room, runs the command, and frees the slot when it exits.
210
237
 
211
238
  Turn off: `BULKHEAD_DISABLE=memory-guard`. Details: [docs/guards/memory-guard.md](docs/guards/memory-guard.md).
239
+
240
+ With `"memoryGuard": { "autoQueue": true }`, nobody has to remember the slot: memory-guard rewrites every heavy command (tests, builds, browser runs, container builds) to run through `bulkhead-slot`, so all sessions wait in one fair queue instead of being blocked. A waiting job prints its position every 30 seconds, and `bulkhead status` shows which session and project each queued job belongs to. With `"memoryGuard": { "subagents": true }`, a new subagent is not started while memory pressure is critical. See [docs/commands.md](docs/commands.md#auto-queue).
212
241
  </details>
213
242
 
214
243
  <details>
215
244
  <summary><strong>checkout-guard</strong>: two sessions in one git tree</summary>
216
245
 
217
- Blocks `git checkout <branch>`, `switch`, `stash`, `reset --hard`, `clean -f`, `rebase`, `merge`, `restore .` and a dirty `pull` when another live session works in the same tree, and suggests `git worktree add`. Also blocks `git add -A`, `git add .` and `git commit -a` there (`stage-all`), which would sweep the other session's half-finished edits into your commit, and `rm -rf` or `git worktree remove` of a folder another live session works in (`remove-session-dir`). On Windows it blocks `git worktree remove --force` on a worktree that holds junctions, which git has followed into the main checkout.
246
+ Blocks `git checkout <branch>`, `switch`, `stash`, `reset --hard`, `clean -f`, `rebase`, `merge`, `restore .` and a dirty `pull` when another live session works in the same tree, and suggests `git worktree add`. Also blocks `git add -A`, `git add .` and `git commit -a` there (`stage-all`; `git add -A src/` and `git add -n` pass), which would sweep the other session's half-finished edits into your commit, and `rm -rf` or `git worktree remove` of a folder another live session works in (`remove-session-dir`). On Windows it blocks `git worktree remove --force` on a worktree that holds junctions, which git has followed into the main checkout.
218
247
 
219
248
  Turn off: `BULKHEAD_DISABLE=checkout-guard`, or one rule with `BULKHEAD_DISABLE=stage-all` or `remove-session-dir`. Details: [docs/guards/checkout-guard.md](docs/guards/checkout-guard.md).
220
249
  </details>
@@ -238,19 +267,30 @@ Turn off: `BULKHEAD_DISABLE=wait-guard`, or raise the limit with `BULKHEAD_WAIT_
238
267
  <details>
239
268
  <summary><strong>orphan-reaper</strong>: processes left by sessions that exited</summary>
240
269
 
241
- When a session exits, a hidden background waiter stops what it started from its tool shells and left running: dev servers and anything else listening on a port, test runners and their workers (Vitest, Jest, Playwright, pytest and xdist, `cargo test`, `go test` and more), watchers such as `tsc --watch` and nodemon, builds such as `next build`, and headless browsers. A pass at session start catches what a crash left behind. Editors, terminals, shared daemons such as Gradle or adb, and anything you started yourself are never touched. `bulkhead reap --dry-run` shows what it would stop and why. Windows, Linux and macOS (macOS tested with fixtures).
270
+ When a session exits, a hidden background waiter stops what it started from its tool shells and left running: dev servers and anything else listening on a port, test runners and their workers (Vitest, Jest, Playwright, pytest and xdist, `cargo test`, `go test` and more), watchers such as `tsc --watch` and nodemon, builds such as `next build`, and headless browsers. A pass at session start catches what a crash left behind. Editors, terminals, shared daemons such as Gradle or adb, and anything you started yourself are never touched. `bulkhead reap --dry-run` shows what it would stop and why. Something you started on purpose to stay up is kept with `BULKHEAD_KEEP=1 <command>` (ssh tunnels, cloudflared, ngrok, Jupyter, Redis, Postgres, MySQL, MongoDB and Docker are kept without it), and the next session tells you in one line what was stopped. Windows, Linux and macOS (macOS tested on a real Mac).
242
271
 
243
272
  Turn off: `BULKHEAD_DISABLE=orphan-reaper`. Details: [docs/guards/orphan-reaper.md](docs/guards/orphan-reaper.md).
244
273
  </details>
245
274
 
246
275
  <details>
247
- <summary><strong>basics</strong>: four mistakes that cannot be undone</summary>
276
+ <summary><strong>basics</strong>: five mistakes that cannot be undone</summary>
248
277
 
249
- `rm-root` (`rm -rf` at a root, a top-level folder such as `/srv`, home, a folder directly in home such as `~/Documents`, or above the repository), `force-push-main` (`git push --force` to `main` or `master`; `--force-with-lease` passes), `discard-changes` (`git reset --hard` or `git clean -fd` that would lose uncommitted work) and `agent-author` (`--author="Claude <noreply@anthropic.com>"`, `git config user.name Claude` and the like, so your commits keep counting as yours).
278
+ `rm-root` (`rm -rf` at a root, a top-level folder such as `/srv`, home, a folder directly in home such as `~/Documents`, or above the repository), `force-push-main` (`git push --force` to `main` or `master`; `--force-with-lease` passes), `protected-delete` (`git push origin :main`, `git push --delete origin main`, `git branch -D main`), `discard-changes` (`git reset --hard` or `git clean -fd` that would lose uncommitted work) and `agent-author` (`--author="Claude <noreply@anthropic.com>"`, `git config user.name Claude` and the like, so your commits keep counting as yours).
250
279
 
251
280
  Turn off: `BULKHEAD_DISABLE=basics`, or one rule by name. Details: [docs/guards/basics.md](docs/guards/basics.md).
252
281
  </details>
253
282
 
283
+ ## Codex, Gemini and other agents
284
+
285
+ If you run Codex CLI, Gemini CLI, Copilot CLI, Cursor, opencode, Amp, Cline, Factory Droid, Windsurf or Devin on the same machine as Claude Code, bulkhead guards their shell commands with the same guards and counts their sessions: an agent's dev server is protected from the others, and `bulkhead status` gets an `AGENT` column.
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>` works for `codex`, `gemini`, `copilot`, `cursor`, `windsurf`, `droid`, `devin`, `cline`, `opencode` and `amp`. Each agent calls `bulkhead hook --agent <name>` before a shell command and gets its own block format back. The agents, config files, what is guarded and the documentation each row was checked against are in [docs/other-agents.md](docs/other-agents.md).
293
+
254
294
  ## How bulkhead relates to other hook packs
255
295
 
256
296
  General safety packs protect a session from dangerous commands. For example, [claude-forge](https://github.com/sangrokjung/claude-forge) includes hooks that filter leaked secrets from output, block piped remote scripts and stop destructive SQL, and [dcg](https://github.com/Dicklesworthstone/destructive_command_guard) and [cc-safety-net](https://github.com/kenryu42/claude-code-safety-net) cover destructive shell and git commands broadly. They are worth using.
@@ -283,7 +323,8 @@ Environment variables still work and win over both files. The simplest place for
283
323
 
284
324
  | Variable | Default | Same as |
285
325
  |---|---|---|
286
- | `BULKHEAD_DISABLE` | empty | Comma-separated guards to turn off: `kill-guard`, `wait-guard`, `memory-guard`, `port-guard`, `checkout-guard`, `disk-guard`, `orphan-reaper`, `basics`, or `all`. Single rules can be turned off too: `rm-root`, `force-push-main`, `discard-changes`, `agent-author`, `stage-all`, `remove-session-dir`. A name here is off whatever the files say |
326
+ | `BULKHEAD_DISABLE` | empty | Comma-separated guards to turn off: `kill-guard`, `wait-guard`, `memory-guard`, `port-guard`, `checkout-guard`, `disk-guard`, `orphan-reaper`, `basics`, or `all`. Single rules can be turned off too: `rm-root`, `force-push-main`, `protected-delete`, `discard-changes`, `agent-author`, `stage-all`, `remove-session-dir`. A name here is off whatever the files say |
327
+ | `BULKHEAD_PRESET` | `normal` | The preset: `quiet`, `normal` or `strict` |
287
328
  | `BULKHEAD_WAIT_MAX_SEC` | 180 | `waitGuard.limitSeconds` |
288
329
  | `BULKHEAD_MIN_FREE_GB` | 3, or 15% of RAM if less | `memory.minFreeGb` |
289
330
  | `BULKHEAD_SLOT_FLOOR_GB` | 5, or 25% of RAM if less | `memory.slotFloorGb` |
@@ -294,32 +335,68 @@ Environment variables still work and win over both files. The simplest place for
294
335
 
295
336
  </details>
296
337
 
338
+ ## When it blocks something you meant
339
+
340
+ A block is a question, not a verdict. Three ways out, from the smallest:
341
+
342
+ 1. Allow it once. Every block message ends with a short code. In your own terminal, run `bulkhead allow 7F3K`. The agent's next identical call (same command, same folder) goes through, once, within 10 minutes. The agent cannot do this for you: bulkhead blocks any attempt to run `bulkhead allow` or to write its state files.
343
+ 2. See why first. `bulkhead explain -- <command>` runs the command through every guard against the live sessions, ports and git tree, without running it, and prints who owns what is in the way, the exact message the agent sees, and how to allow it.
344
+ 3. Allow it for good, or change how much it blocks. Put the rule in `.claude/bulkhead.json` (the table below), or pick a preset: `bulkhead config --preset quiet` keeps only the cross-session guards and the two rules that cannot be undone, `strict` turns everything on with tighter limits, `normal` is the default. Your own keys and environment variables win over a preset.
345
+
346
+ ### Blocked something you meant? cheat sheet
347
+
348
+ The one-time way is always `bulkhead allow <code>`, run by you. The permanent way, for the rule that fired:
349
+
350
+ | Guard | Rule | Permanent, in `.claude/bulkhead.json` |
351
+ |---|---|---|
352
+ | kill-guard | `pkill`, `taskkill-im`, `stop-process-name` and the other kills by name | `{"killGuard":{"allowNames":["ffmpeg"]}}` |
353
+ | kill-guard | `other-session-pid`: a PID in another session's tree | `{"killGuard":{"ownerCheck":false}}` |
354
+ | kill-guard | `mass-kill`, `docker-mass`, `device-kill`, `app-quit`, `service-stop`: the other rules (only while another session runs) | `{"killGuard":{"massKill":false}}`, and likewise (`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` and the other tree changes | `{"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` | cannot be allowed once; only you can approve |
368
+
369
+ The same switch as an environment variable is `BULKHEAD_DISABLE=<guard or rule name>`. Everything is in [docs/configuration.md](docs/configuration.md) and [docs/commands.md](docs/commands.md).
370
+
297
371
  ## See what is running
298
372
 
299
373
  `bulkhead` is on the Bash tool's `PATH` next to `bulkhead-slot`. Inside Claude Code, `/bulkhead:scan [30d]`, `/bulkhead:status`, `/bulkhead:log [24h|7d]`, `/bulkhead:doctor` and `/bulkhead:reap` run the matching command and summarize it. In your own terminal, run `npx claude-bulkhead top`, or install it once with `npm install -g claude-bulkhead` and run `bulkhead top`. The npm package is the same code as the plugin. Every command that prints a table takes `--json`. The full reference is [docs/commands.md](docs/commands.md).
300
374
 
301
375
  ### bulkhead status
302
376
 
303
- Every Claude Code session on the machine, the memory its process tree uses, what it has listening on a port, the jobs held in bulkhead-slot and the ones waiting, and free RAM:
377
+ Every Claude Code session on the machine: what it is doing (working, waiting for you, needs permission), its git branch and whether that is a worktree, the memory its process tree uses, what it has listening on a port, the jobs held in bulkhead-slot and the ones waiting, free RAM, and MCP servers that run as several copies across sessions:
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` puts the sessions that need you first. `bulkhead port --stable` prints a port that belongs to the current git worktree and is the same every time. To get a desktop notification when a session starts waiting, set `notify.command` ([commands.md](docs/commands.md#session-state-and-notifications)).
399
+
323
400
  `bulkhead slot ...` is the same as `bulkhead-slot ...`.
324
401
 
325
402
  ### bulkhead top
@@ -374,31 +451,44 @@ PROJECT COUNT
374
451
 
375
452
  ### bulkhead doctor
376
453
 
377
- Checks Node, the plugin files, the state directory, the config files, each guard (on or off, and why), a real hook run, the process reader with its timing, the session files, the RAM limits computed for this machine, held slots, the event log and git. The exit code is 1 when any check fails.
454
+ Checks Node, the plugin files, the state directory, the config files, each guard (on or off, and why), the process reader with its timing, the session files, the RAM limits computed for this machine, held slots, the event log and git. Then an Enforcement section proves the installed hooks block: the install and `enabledPlugins`, duplicate installs, settings that turn hooks off, `node` on the hook `PATH`, and a real run of each guard's hook command against a canary, with its latency. The exit code is 1 when any check fails.
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 are the live entries in `~/.claude/sessions`, heavy is the number of jobs holding a bulkhead-slot, and free is the same measure memory-guard uses. It does not scan processes, takes under 100 ms, and caches the line for 5 seconds in the state directory.
521
+ Sessions are the live entries in `~/.claude/sessions`, heavy is the number of jobs holding a bulkhead-slot, and free is the same measure memory-guard uses. When sessions are waiting for you, the line adds `2 waiting` after the session count. It does not list every process (one `ps` for the session pids only), takes about 100 ms from cold on a Mac with 650 processes, and caches the line for 5 seconds in the state directory, written through a temporary file so concurrent sessions never read half of it.
432
522
 
433
523
  A plugin cannot set the main status line (plugin settings only support `agent` and `subagentStatusLine`), so add it to `~/.claude/settings.json` yourself:
434
524
 
@@ -447,7 +537,7 @@ This needs `npm install -g claude-bulkhead`, which puts `bulkhead-statusline` on
447
537
  ## FAQ
448
538
 
449
539
  **Does it slow Claude Code down?**
450
- A little. Each guard is one short Node process. On a Windows 11 desktop with Node 24, `node scripts/bench-hooks.js 15` measured 40 to 49 ms per guard on an allowed command (median of 15 runs), and 33 ms for starting Node alone. Writing a block to the event log adds no measurable time. The guards only do slow work, such as reading the process list (a second or two on Windows), when the command matches: a dev server start on a port that already answers, a kill, a heavy command. The status line takes under 100 ms and is cached for 5 seconds.
540
+ A little. All the Bash guards run in one short Node process with no dependencies, about 76 ms per tool call on a MacBook Pro (Node 24), of which 32 ms is starting Node. When 10 sessions make a tool call at the same moment, each waits about 89 ms (318 ms when every guard was its own process, as in 0.2). Writing a block to the event log adds no measurable time. The guards only do slow work, such as reading the process list (a second or two on Windows), when the command matches: a dev server start on a port that already answers, a kill, a heavy command. The status line takes about 100 ms cold and is cached for 5 seconds.
451
541
 
452
542
  **It blocked something it should not have. What do I do?**
453
543
  Run `bulkhead log` to see the exact command and reason. Let it through in the project config, `<repo>/.claude/bulkhead.json`: `killGuard.allowNames` for a program name, `portGuard.ignorePorts` for a shared port, `waitGuard.limitSeconds` for a longer wait, or `"enabled": false` for the whole guard in that project. See [docs/configuration.md](docs/configuration.md). Then please open a [false positive issue](https://github.com/dooly6276/claude-bulkhead/issues/new?template=false-positive.yml) with the line from `bulkhead log`, so the pattern can be fixed for everyone.
@@ -456,7 +546,7 @@ Run `bulkhead log` to see the exact command and reason. Let it through in the pr
456
546
  Yes. kill-guard, wait-guard, memory-guard and basics protect one session and its subagents just as well. port-guard and checkout-guard only act when another session is alive, and orphan-reaper cleans up after sessions that have exited.
457
547
 
458
548
  **Which systems does it support?**
459
- Windows, Linux and macOS, with Node 18 or later. orphan-reaper on macOS is tested with fixtures of `ps` output but not yet on a real Mac, and on Linux it needs a Claude Code version that sets `CLAUDE_PID` for tool shells. On Windows, `bulkhead-slot` runs commands through Git Bash when it is installed and through `cmd.exe` otherwise.
549
+ Windows, Linux and macOS, with Node 18 or later. orphan-reaper on macOS is tested with fixtures of `ps` output and on a real Mac (a stand-in session was ended and only its leftovers were stopped), and on Linux it needs a Claude Code version that sets `CLAUDE_PID` for tool shells. On Windows, `bulkhead-slot` runs commands through Git Bash when it is installed and through `cmd.exe` otherwise.
460
550
 
461
551
  **Does it send anything anywhere?**
462
552
  No. There is no telemetry and no network call. The only socket it opens is port-guard's connection attempt to `localhost` to see whether a port answers. Logs stay in the state directory. The one exception is `bulkhead remote`, which connects over SSH to a machine you configured, and only when you run it.
@@ -477,6 +567,8 @@ More questions, including how the guards can be bypassed and how the RAM numbers
477
567
  /plugin marketplace remove bulkhead
478
568
  ```
479
569
 
570
+ If you installed the npm package instead, run `npm uninstall -g claude-bulkhead`. A SessionEnd waiter that is already running exits by itself within 3 minutes, so nothing keeps running after you uninstall.
571
+
480
572
  Then delete the state directory (`~/.claude/bulkhead`, or `BULKHEAD_STATE_DIR` if you set it) and remove the `statusLine` entry from `~/.claude/settings.json` if you added one.
481
573
 
482
574
  ## Using bulkhead? Show it