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.
- package/CHANGELOG.md +51 -0
- package/README.es.md +142 -50
- package/README.ja.md +142 -50
- package/README.ko.md +143 -51
- package/README.md +142 -50
- package/README.pt-BR.md +142 -50
- package/README.zh-CN.md +154 -62
- package/hooks/hooks.json +68 -8
- package/package.json +5 -2
- package/src/agent-hook.js +162 -0
- package/src/agent-setup.js +256 -0
- package/src/agents.js +89 -0
- package/src/allow-cli.js +65 -0
- package/src/allow-hint.js +62 -0
- package/src/allow-once.js +152 -0
- package/src/auto-queue.js +93 -0
- package/src/bash-guards.js +36 -0
- package/src/basics.js +46 -27
- package/src/checkout-guard.js +342 -74
- package/src/cli-util.js +13 -1
- package/src/cli.js +40 -4
- package/src/config-check.js +21 -4
- package/src/config-cli.js +75 -10
- package/src/config.js +106 -9
- package/src/dev-servers.js +361 -61
- package/src/disk-guard.js +8 -8
- package/src/doctor-enforcement.js +273 -0
- package/src/doctor.js +19 -9
- package/src/edit-guard.js +196 -0
- package/src/events.js +30 -5
- package/src/explain.js +251 -0
- package/src/git-info.js +55 -0
- package/src/gitargs.js +82 -0
- package/src/hook.js +38 -6
- package/src/kill-extras.js +219 -0
- package/src/kill-guard.js +228 -77
- package/src/kill-owners.js +116 -0
- package/src/log-cli.js +15 -10
- package/src/mcp.js +131 -0
- package/src/memory-guard.js +126 -24
- package/src/memory.js +21 -2
- package/src/notices.js +95 -0
- package/src/orphan-reaper.js +58 -11
- package/src/port-cli.js +78 -3
- package/src/port-guard.js +80 -68
- package/src/port-kills.js +198 -0
- package/src/pressure.js +75 -0
- package/src/processes.js +44 -12
- package/src/scan-report.js +166 -0
- package/src/scan.js +87 -18
- package/src/secrets-guard.js +301 -0
- package/src/self-allow.js +73 -0
- package/src/session-info.js +121 -0
- package/src/session-state.js +176 -0
- package/src/sessions.js +109 -20
- package/src/shell.js +279 -19
- package/src/slot-cli.js +92 -21
- package/src/slots.js +61 -13
- package/src/stable-port.js +61 -0
- package/src/status.js +55 -17
- package/src/statusline.js +45 -16
- package/src/subagent-guard.js +24 -0
- package/src/top.js +36 -11
- package/src/wait-guard.js +96 -32
- package/src/worktree-cli.js +454 -0
- 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
|
|
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`).
|
|
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)
|
|
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
|
-
| **[
|
|
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
|
|
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>:
|
|
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
|
|
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
|
|
308
|
-
18672
|
|
309
|
-
:3000 node
|
|
310
|
-
21888
|
|
311
|
-
26448
|
|
312
|
-
:3001 node
|
|
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),
|
|
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
|
|
382
|
-
OK
|
|
383
|
-
OK state dir
|
|
384
|
-
OK config
|
|
385
|
-
OK
|
|
386
|
-
OK
|
|
387
|
-
OK
|
|
388
|
-
OK
|
|
389
|
-
OK
|
|
390
|
-
OK
|
|
391
|
-
OK
|
|
392
|
-
OK
|
|
393
|
-
OK
|
|
394
|
-
OK
|
|
395
|
-
OK
|
|
396
|
-
OK
|
|
397
|
-
OK
|
|
398
|
-
OK
|
|
399
|
-
OK
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|