claude-bulkhead 0.0.0-stage → 0.2.2

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 (48) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/LICENSE +21 -0
  3. package/README.es.md +498 -0
  4. package/README.ja.md +498 -0
  5. package/README.ko.md +498 -0
  6. package/README.md +497 -2
  7. package/README.pt-BR.md +498 -0
  8. package/README.zh-CN.md +498 -0
  9. package/bin/bulkhead +3 -0
  10. package/bin/bulkhead-slot +3 -0
  11. package/bin/bulkhead-statusline +3 -0
  12. package/hooks/hooks.json +39 -0
  13. package/package.json +52 -4
  14. package/src/basics.js +236 -0
  15. package/src/checkout-guard.js +296 -0
  16. package/src/cli-util.js +79 -0
  17. package/src/cli.js +41 -0
  18. package/src/config-check.js +29 -0
  19. package/src/config-cli.js +51 -0
  20. package/src/config.js +236 -0
  21. package/src/dev-servers.js +223 -0
  22. package/src/disk-guard.js +200 -0
  23. package/src/doctor.js +255 -0
  24. package/src/events.js +108 -0
  25. package/src/hook.js +94 -0
  26. package/src/kill-guard.js +435 -0
  27. package/src/log-cli.js +146 -0
  28. package/src/memory-guard.js +145 -0
  29. package/src/memory.js +76 -0
  30. package/src/orphan-reaper.js +527 -0
  31. package/src/paths.js +31 -0
  32. package/src/port-cli.js +59 -0
  33. package/src/port-guard.js +142 -0
  34. package/src/processes.js +181 -0
  35. package/src/reap-cli.js +56 -0
  36. package/src/remote-sh.js +478 -0
  37. package/src/remote.js +1034 -0
  38. package/src/remotes.js +126 -0
  39. package/src/scan.js +604 -0
  40. package/src/sessions.js +147 -0
  41. package/src/shell.js +597 -0
  42. package/src/slot-cli.js +150 -0
  43. package/src/slots.js +168 -0
  44. package/src/status.js +111 -0
  45. package/src/statusline.js +69 -0
  46. package/src/top-worker.js +14 -0
  47. package/src/top.js +305 -0
  48. package/src/wait-guard.js +263 -0
package/README.md CHANGED
@@ -1,3 +1,498 @@
1
- # Temporary Holding Version
1
+ <h1 align="center">claude-bulkhead</h1>
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center">
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
+ </p>
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>
8
+
9
+ <p align="center">
10
+ <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>
11
+ <img src="https://img.shields.io/badge/node-18%2B-34d6b4?style=flat-square" alt="Node 18+">
12
+ <img src="https://img.shields.io/badge/dependencies-0-34d6b4?style=flat-square" alt="zero dependencies">
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-34d6b4?style=flat-square" alt="MIT license"></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <img src="docs/demo.gif" alt="Two Claude Code sessions work on two worktrees of one app. Session A serves checkout on port 3000. Session B is told to kill all node processes and restart its search server; bulkhead's kill-guard blocks the command, Claude restarts only its own server, and checkout keeps running with the same PID." width="100%">
18
+ </p>
19
+
20
+ <p align="center"><sub><a href="README.ko.md">한국어</a> | <a href="README.zh-CN.md">简体中文</a> | <a href="README.ja.md">日本語</a> | <a href="README.es.md">Español</a> | <a href="README.pt-BR.md">Português (Brasil)</a></sub></p>
21
+
22
+ ## Install
23
+
24
+ Inside Claude Code:
25
+
26
+ ```
27
+ /plugin marketplace add dooly6276/claude-bulkhead
28
+ /plugin install bulkhead@bulkhead
29
+ ```
30
+
31
+ Then see what it would have stopped in your own last month, without changing anything:
32
+
33
+ ```
34
+ /bulkhead:scan 30d
35
+ ```
36
+
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.
38
+
39
+ ## What it stops
40
+
41
+ | Without bulkhead | With bulkhead |
42
+ |---|---|
43
+ | An agent restarts its dev server with `pkill node`, and every other worktree's server dies with it. | kill-guard blocks the command and tells the agent how to find and stop its own PID. |
44
+ | Two sessions share a checkout. One runs `git add -A && git commit`, and the other one's half-finished edits ship in that commit. | checkout-guard blocks it and asks for the file names. |
45
+ | Two worktrees run `npm run dev`. The second moves to 3001, but its agent keeps testing `localhost:3000` and sees the other branch. | port-guard blocks the second start and names a free port. |
46
+ | Three sessions start `next build`, `npm test` and Playwright in the same minute, and the machine freezes. | memory-guard blocks heavy commands when RAM is low. Through `bulkhead-slot`, the third job waits in a queue until there is room. |
47
+
48
+ <details>
49
+ <summary>Five more</summary>
50
+
51
+ | Without bulkhead | With bulkhead |
52
+ |---|---|
53
+ | A session checks out another branch in a tree that a second session is editing. | checkout-guard blocks it and suggests `git worktree add`. |
54
+ | A session exits, and its dev servers, test workers and watchers keep running and holding RAM and ports. | orphan-reaper stops them within seconds of the session exiting. |
55
+ | A subagent polls in the foreground for ten minutes. The session cannot answer you, and the prompt cache expires. | wait-guard blocks the loop and asks for `run_in_background: true`. |
56
+ | An agent wants to know what is using disk space and runs ten `du` at once over your home folder. Every session's builds and `git status` crawl for minutes. | disk-guard blocks it and asks for one folder at a time. |
57
+ | A "clean up the worktrees" task runs `git worktree remove` on a worktree a second session is still working in. | checkout-guard blocks it and names the session that is still there. |
58
+
59
+ </details>
60
+
61
+ With a single session, kill-guard, memory-guard, wait-guard, disk-guard and basics (no `rm -rf ~`, no force-push to `main`, no `reset --hard` over uncommitted work) still apply. port-guard and checkout-guard act once a second session or subagent is alive.
62
+
63
+ ## What is it?
64
+
65
+ <p align="center">
66
+ <picture>
67
+ <source media="(prefers-color-scheme: light)" srcset="docs/hero-light.svg">
68
+ <img src="docs/hero.svg" alt="Several Claude Code sessions in separate git worktrees on one machine. kill-guard blocks pkill node, port-guard moves a second dev server to a free port, disk-guard stops ten parallel du scans, checkout-guard stops git add -A from sweeping another session's edits into a commit, and bulkhead-slot queues a third heavy job so free RAM stays above the floor. bulkhead scan then counts what it would have stopped in past sessions." width="100%">
69
+ </picture>
70
+ </p>
71
+
72
+ Claude Code hooks are small programs that Claude Code runs before every shell command, and that can stop the command and tell Claude why. bulkhead is a set of them. Before each Bash or PowerShell call it checks the command against what the other sessions on the machine are doing: which processes and ports belong to which session, how much RAM is left, and which git tree each session works in. When the command would hurt another session, it is blocked, and Claude gets a message with the safer command to run instead. Claude reads it and carries on, usually without asking you.
73
+
74
+ ## Do you need it? Ask your transcripts
75
+
76
+ You do not have to guess. Claude Code keeps every session's transcript in `~/.claude/projects`, and `bulkhead scan` replays the shell commands in them against the guards: how many sessions you really run at once, and how often they already did the things bulkhead blocks.
77
+
78
+ <p align="center">
79
+ <picture>
80
+ <source media="(prefers-color-scheme: light)" srcset="docs/scan-light.svg">
81
+ <img src="docs/scan.svg" alt="bulkhead scan reads a month of transcripts, reports the most sessions that ran at once, and lists per guard and rule how many commands would have been stopped, in how many sessions, and how many a hook or the user already stopped." width="100%">
82
+ </picture>
83
+ </p>
84
+
85
+ ```
86
+ /plugin install bulkhead@bulkhead # then, inside Claude Code:
87
+ /bulkhead:scan 30d
88
+ ```
89
+
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).
91
+
92
+ ## Why this exists
93
+
94
+ A common way to speed up work with Claude Code is to run several sessions on one repository, each in its own git worktree, each with its own subagents. git keeps the branches apart. The machine does not: every session shares the same ports, the same process table and the same RAM. That is where they collide.
95
+
96
+ - Two worktrees of the same app both run `npm run dev`. The second one fails on port 3000, or quietly moves to 3001 while its agent keeps testing `localhost:3000` and sees the other branch.
97
+ - An agent restarts its own server with `pkill node` or `taskkill /IM node.exe` and takes down the dev servers and test runs of every other worktree, and sometimes its own session.
98
+ - Three sessions start `npm test`, `next build` and a Playwright run in the same minute, and the machine stops responding.
99
+ - A session ends and the dev servers it started keep running.
100
+
101
+ The Claude Code tracker has many reports of exactly this:
102
+
103
+ - [#13941](https://github.com/anthropics/claude-code/issues/13941): asked to restart a dev server, Claude ran `taskkill //IM node.exe` or `pkill node` and killed its own session.
104
+ - [#34754](https://github.com/anthropics/claude-code/issues/34754): `taskkill //F //IM python.exe` killed two long-running simulations, after the user had marked their PIDs as off limits.
105
+ - [#43225](https://github.com/anthropics/claude-code/issues/43225): asked to restart one bot, Claude force-killed every Python process on the machine, about 20 services.
106
+ - [#53229](https://github.com/anthropics/claude-code/issues/53229): told to stop only unused dev servers, an agent also killed a gateway and a Postgres process it had not identified.
107
+ - [#15487](https://github.com/anthropics/claude-code/issues/15487): 24 subagent processes saturated a 4 GB VPS until it had to be rebooted; a commenter froze a 24 GB Mac the same way.
108
+ - [#11502](https://github.com/anthropics/claude-code/issues/11502) and [#25180](https://github.com/anthropics/claude-code/issues/25180): processes started by agents outlive them, 60 Node processes in one report and 550 pytest workers in the other.
109
+
110
+ claude-bulkhead is a set of small hooks that block or clean up each of these. Each one can be turned off on its own. The name is from the bulkhead pattern: one compartment taking on water should not sink the ship.
111
+
112
+ ## What it does
113
+
114
+ | | |
115
+ |---|---|
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. |
117
+ | **[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. |
120
+ | **[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`. |
122
+
123
+ 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
+
125
+ ## More demos
126
+
127
+ ![bulkhead status, then port-guard, kill-guard and wait-guard blocking an agent](docs/demo.svg)
128
+
129
+ A scripted run of `bulkhead status`, port-guard, kill-guard and wait-guard, recorded with [docs/demo.sh](docs/demo.sh). The two sessions are stand-in processes named `claude`; everything printed comes from the hook scripts and `bulkhead status`. The recording at the top of this page is a real Claude Code session; how it was made is in [docs/demo.tape](docs/demo.tape).
130
+
131
+ ## Quick start
132
+
133
+ 1. Install the plugin as above and start a new Claude Code session.
134
+ 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.
136
+ 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
+
138
+ 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
+
140
+ ## Run heavy jobs on another machine (optional)
141
+
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.
143
+
144
+ <p align="center">
145
+ <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%">
146
+ </p>
147
+
148
+ ```
149
+ bulkhead remote add buildbox dev@192.168.1.20 --ssh-option=IdentityFile=~/.ssh/buildbox
150
+ bulkhead remote run -- npm test
151
+ ```
152
+
153
+ ```
154
+ [bulkhead remote] buildbox: 3f2a1c9 already there, 3 changed files (24 KB), 1 deleted
155
+ [bulkhead remote] buildbox copy a /home/dev/bulkhead-remote/acme-web-3f9a1c port 7172
156
+ ...test output...
157
+ [bulkhead remote] exit 0 after 1m 12s (copy a)
158
+ ```
159
+
160
+ - **What is sent.** The current commit as a git bundle (only the commits the remote does not have yet) and your uncommitted changes, deletions included. Ignored files stay here unless you name them with `--files`. Nothing outside the repository is sent.
161
+ - **Where it runs.** In one of up to 8 copies of the repository on the remote, so parallel runs do not share a working tree. `node_modules` is kept between runs. Each run gets its own port in `$BULKHEAD_PORT`.
162
+ - **Room.** Locks on the remote hold a copy and a job slot, and `--gb=N` waits until N GB is free there. When there is no room within `--wait` minutes, it exits 3.
163
+ - **Cleanup.** When the command ends, or the SSH connection drops (Ctrl-C, a tool timeout), everything it started on the remote is stopped, including a server left on its port.
164
+ - **The remote** needs only an SSH server, `bash`, `git` and `tar`. Linux, macOS and Windows with Git for Windows all work.
165
+
166
+ `bulkhead remote fetch PATH` brings files back from the copy the last run used, and `bulkhead remote status` shows each remote's free RAM and running jobs. In Claude Code, `/bulkhead:remote <command>` runs a command there in the background. Setup, options and exit codes: [docs/remote.md](docs/remote.md).
167
+
168
+ ## How it works
169
+
170
+ ```mermaid
171
+ flowchart LR
172
+ CC["Claude Code session"] -->|"PreToolUse: Bash, PowerShell"| G["kill-guard, wait-guard, memory-guard,<br>port-guard, checkout-guard, disk-guard, basics"]
173
+ CC -->|"SessionEnd, SessionStart"| R["orphan-reaper (background)"]
174
+ G -->|"read"| P["process table, listening ports,<br>~/.claude/sessions"]
175
+ G <-->|"read, append"| S[("state dir ~/.claude/bulkhead<br>slots, guards.log, events.jsonl")]
176
+ B["bulkhead-slot"] <--> S
177
+ G -->|"exit 2 + message: blocked"| CC
178
+ G -->|"exit 0: allowed"| CC
179
+ G -.->|"any error: exit 0"| CC
180
+ ```
181
+
182
+ Each guard is a separate Node script with no dependencies. Claude Code pipes the tool call to it as JSON. The guard reads the command text, and only when the command matches does it look further: at the process table, the listening ports, the session files Claude Code keeps in `~/.claude/sessions`, free RAM, or `git status`. A block is exit code 2 with a message that tells Claude what to do instead. Any error inside a guard lets the call through, so a broken guard never stops your work.
183
+
184
+ Every session on the machine shares one state directory. bulkhead-slot keeps its RAM reservations there, and every block is recorded there. More detail, including the state directory layout and how sessions are found on each OS, is in [docs/architecture.md](docs/architecture.md).
185
+
186
+ ## Guards
187
+
188
+ Each guard has a page with the exact commands it blocks, what passes, the message Claude sees, and its limits. The short version:
189
+
190
+ <details>
191
+ <summary><strong>kill-guard</strong>: kills by program name</summary>
192
+
193
+ Blocks `taskkill /IM`, `taskkill /FI` without a PID, `Stop-Process -Name`, `Get-Process node | Stop-Process`, `pkill`, `killall`, `kill $(pgrep node)`, `wmic ... delete` and similar when they target a program other sessions share, such as `node`, `python`, `java`, `chrome`, `git` or `vite`. Kills by PID and by project path pass. Kills by port pass too, unless another session owns the port; port-guard checks those. Claude is told how to find its own PIDs.
194
+
195
+ Turn off: `BULKHEAD_DISABLE=kill-guard`. Details: [docs/guards/kill-guard.md](docs/guards/kill-guard.md).
196
+ </details>
197
+
198
+ <details>
199
+ <summary><strong>port-guard</strong>: ports owned by another session</summary>
200
+
201
+ Blocks starting a dev server (`npm run dev`, `next dev`, `vite`, `uvicorn`, `rails s` and more) on a port that another Claude session's process tree is listening on, and suggests a free port. It also blocks kill-by-port commands such as `npx kill-port 3000`, `fuser -k 3000/tcp` and `lsof -ti :3000 | xargs kill` when another session owns that port.
202
+
203
+ Turn off: `BULKHEAD_DISABLE=port-guard`. Details: [docs/guards/port-guard.md](docs/guards/port-guard.md).
204
+ </details>
205
+
206
+ <details>
207
+ <summary><strong>memory-guard and bulkhead-slot</strong>: RAM</summary>
208
+
209
+ 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
+
211
+ Turn off: `BULKHEAD_DISABLE=memory-guard`. Details: [docs/guards/memory-guard.md](docs/guards/memory-guard.md).
212
+ </details>
213
+
214
+ <details>
215
+ <summary><strong>checkout-guard</strong>: two sessions in one git tree</summary>
216
+
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.
218
+
219
+ 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
+ </details>
221
+
222
+ <details>
223
+ <summary><strong>disk-guard</strong>: whole-disk and parallel scans</summary>
224
+
225
+ Blocks `du`, `ncdu`, `dust`, `ls -R`, `Get-ChildItem -Recurse` and `dir /s` over the whole drive, the home folder or every user's home (`du -sh ~`, `du -sh ~/*`, `ncdu /`), `find /` and `find ~` without `-maxdepth`, and disk scans run in parallel wherever they point: `xargs -P 10 du`, GNU `parallel`, `du ... &` in a loop, `ForEach-Object -Parallel`. One folder at a time passes (`du -sh node_modules`, `du -sh ~/Library/Caches`).
226
+
227
+ Turn off: `BULKHEAD_DISABLE=disk-guard`. Details: [docs/guards/disk-guard.md](docs/guards/disk-guard.md).
228
+ </details>
229
+
230
+ <details>
231
+ <summary><strong>wait-guard</strong>: long foreground waits</summary>
232
+
233
+ Blocks a foreground command whose sleeps can add up to more than 180 seconds. The prompt cache expires after about five minutes without a request, so a subagent polling in the foreground rewrites the whole context every time it wakes. Claude is told to run the loop with `run_in_background: true`. Sleeps in a background job (`sleep 600 &`) pass. With the default 120 second tool timeout a wait cannot pass the default limit, so it blocks only when the call raises the timeout.
234
+
235
+ Turn off: `BULKHEAD_DISABLE=wait-guard`, or raise the limit with `BULKHEAD_WAIT_MAX_SEC`. Details: [docs/guards/wait-guard.md](docs/guards/wait-guard.md).
236
+ </details>
237
+
238
+ <details>
239
+ <summary><strong>orphan-reaper</strong>: processes left by sessions that exited</summary>
240
+
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).
242
+
243
+ Turn off: `BULKHEAD_DISABLE=orphan-reaper`. Details: [docs/guards/orphan-reaper.md](docs/guards/orphan-reaper.md).
244
+ </details>
245
+
246
+ <details>
247
+ <summary><strong>basics</strong>: four mistakes that cannot be undone</summary>
248
+
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).
250
+
251
+ Turn off: `BULKHEAD_DISABLE=basics`, or one rule by name. Details: [docs/guards/basics.md](docs/guards/basics.md).
252
+ </details>
253
+
254
+ ## How bulkhead relates to other hook packs
255
+
256
+ 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.
257
+
258
+ bulkhead covers a different problem: sessions on one machine getting in each other's way through process ownership, ports, shared RAM and a shared git tree. Only its `basics` rules overlap with those packs, and you can turn them off with `BULKHEAD_DISABLE=basics` if another pack already covers them. bulkhead runs alongside other hooks: Claude Code runs every hook that matches a tool call, and the call is stopped if any of them blocks it.
259
+
260
+ ## Configuration
261
+
262
+ The defaults need no setup. To change them, use a config file: `~/.claude/bulkhead/config.json` for the machine, and `<repo>/.claude/bulkhead.json` for one project (found by walking up to the folder with `.git`). Project settings win over the global file.
263
+
264
+ A project file can take names off kill-guard's block list, ignore ports or turn a guard off for that repository. Read `.claude/bulkhead.json` in a repository you clone before you work in it, as you would its other config; `bulkhead config` shows every setting and the file it came from. Remote hosts and ssh options are read from the global file only.
265
+
266
+ For example, a project that runs `ffmpeg` on purpose and has a slow integration suite:
267
+
268
+ ```json
269
+ {
270
+ "killGuard": { "allowNames": ["ffmpeg"] },
271
+ "waitGuard": { "limitSeconds": 300 },
272
+ "portGuard": { "ignorePorts": [9229] },
273
+ "basics": { "protectedBranches": ["main", "release"] }
274
+ }
275
+ ```
276
+
277
+ Every guard can be turned off with `"<guard>": { "enabled": false }`. `bulkhead config` prints every setting, its value and where it came from. A broken file is ignored, the guards keep their defaults, and the session shows one notice at startup. All keys are in [docs/configuration.md](docs/configuration.md), and [config.example.json](config.example.json) sets each of them.
278
+
279
+ Environment variables still work and win over both files. The simplest place for them is the `env` block of `~/.claude/settings.json`.
280
+
281
+ <details>
282
+ <summary>All environment variables</summary>
283
+
284
+ | Variable | Default | Same as |
285
+ |---|---|---|
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 |
287
+ | `BULKHEAD_WAIT_MAX_SEC` | 180 | `waitGuard.limitSeconds` |
288
+ | `BULKHEAD_MIN_FREE_GB` | 3, or 15% of RAM if less | `memory.minFreeGb` |
289
+ | `BULKHEAD_SLOT_FLOOR_GB` | 5, or 25% of RAM if less | `memory.slotFloorGb` |
290
+ | `BULKHEAD_MAX_COMMIT_PCT` | 75 | `memory.maxCommitPct` (Windows only) |
291
+ | `BULKHEAD_NO_LOG` | empty | `log.enabled: false` when set to `1` |
292
+ | `BULKHEAD_STATE_DIR` | `~/.claude/bulkhead` | Where slots, the event log, `guards.log` and the global config file live |
293
+ | `BULKHEAD_SSH` | `ssh` on `PATH` | The ssh client `bulkhead remote` runs |
294
+
295
+ </details>
296
+
297
+ ## See what is running
298
+
299
+ `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
+
301
+ ### bulkhead status
302
+
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:
304
+
305
+ ```
306
+ $ 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
313
+
314
+ SLOT GB PID AGE COMMAND
315
+ slot-1 4 30112 6 min npx playwright test
316
+
317
+ WAITING GB PID FOR COMMAND
318
+ #1 3 30200 2 min npm run build
319
+
320
+ 9.9 GB free of 31.8 GB, commit 68%
321
+ ```
322
+
323
+ `bulkhead slot ...` is the same as `bulkhead-slot ...`.
324
+
325
+ ### bulkhead top
326
+
327
+ The same view, live and full screen. It refreshes every 2 seconds on Linux and macOS and every 4 seconds on Windows, where one process scan takes 1 to 2 seconds. The scan runs in a worker thread, so keys never wait for it. `q`, `Esc` or `Ctrl-C` quit, `r` refreshes.
328
+
329
+ ```
330
+ bulkhead top 14:30:05 scan 1.4s every 4s q quit r refresh
331
+ RAM ████████████████░░░░░░░░░░░░░░ 9.9 GB free of 31.8 GB commit 68%
332
+
333
+ SESSIONS 3
334
+ PID STARTED PROJECT RSS PROCS PORTS
335
+ 18672 Oct 12 17:49 ~/src/acme-web 1.4 GB ██████████ 12 :3000 :24678
336
+ 21888 Oct 12 17:52 ~/src/acme-web-auth 353 MB ███░░░░░░░ 3 -
337
+ 26448 03:43 ~/src/acme-web-checkout 625 MB ████░░░░░░ 5 :3001
338
+
339
+ HEAVY JOBS 1 running, 1 waiting
340
+ slot-1 4 GB 30112 6 min npx playwright test
341
+ wait #1 3 GB 30200 2 min npm run build (this machine has 16 GB; one heavy job at a time)
342
+
343
+ RECENT BLOCKS
344
+ 14:29 kill-guard taskkill-im ~/src/acme-web-auth taskkill /F /IM node.exe
345
+ 14:12 wait-guard poll-loop ~/src/acme-web-checkout until [ -f out/DONE ]; do sleep 30; done
346
+ ```
347
+
348
+ On a narrow terminal it drops columns instead of wrapping. When the output is not a terminal, or with `--once`, it prints one snapshot and exits.
349
+
350
+ ### bulkhead log
351
+
352
+ Every block, and every process orphan-reaper stops, is one JSON line in `<state dir>/events.jsonl` with the time, guard, rule, session, project and the command cut to 200 characters. The file stays on your machine, rotates at 1 MB, and keeps one old file. `BULKHEAD_NO_LOG=1` turns it off.
353
+
354
+ ```
355
+ $ bulkhead log --summary --since 7d
356
+ 10 blocks in the last 7 days, 3 projects, 4 sessions
357
+
358
+ GUARD RULE COUNT LAST
359
+ wait-guard poll-loop 3 06:40 ████████████████████████
360
+ wait-guard long-sleep 2 06:40 ████████████████
361
+ basics force-push-main 1 06:40 ████████
362
+ basics rm-root 1 06:40 ████████
363
+ kill-guard pkill 1 06:40 ████████
364
+ kill-guard taskkill-im 1 06:40 ████████
365
+ memory-guard slot-files 1 06:40 ████████
366
+
367
+ PROJECT COUNT
368
+ ~/src/acme-web 4
369
+ ~/src/acme-web-auth 3
370
+ ~/src/acme-web-checkout 3
371
+ ```
372
+
373
+ `bulkhead log -n 20` lists the events themselves, `--since 30m|24h|7d|2w|<date>` limits the time, and `--guard kill-guard` limits the guard.
374
+
375
+ ### bulkhead doctor
376
+
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.
378
+
379
+ ```
380
+ $ 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
402
+ ```
403
+
404
+ ### bulkhead port
405
+
406
+ Prints a port that nothing listens on, checked on IPv4 and IPv6, so a second worktree can start its server without guessing:
407
+
408
+ ```
409
+ npm run dev -- --port $(bulkhead port --near 3000)
410
+ ```
411
+
412
+ ### bulkhead reap
413
+
414
+ Runs orphan-reaper now. `--dry-run` lists what it would stop, the rule that matched and why the session counts as exited, without stopping anything:
415
+
416
+ ```
417
+ $ bulkhead reap --dry-run
418
+ would stop 30112 test-runner node ~/src/acme-web/node_modules/vitest/dist/workers/forks.js
419
+ Vitest and its worker pool (tinypool); its session has exited and removed snapshot-bash-1791860359256-ezwkie.sh; 6 processes from 30080
420
+ 1 tree would be stopped. Run `bulkhead reap` to stop it.
421
+ ```
422
+
423
+ ### Status line
424
+
425
+ `bulkhead-statusline` prints one line for the Claude Code status line:
426
+
427
+ ```
428
+ 3 sessions 2 heavy 9.1 GB free
429
+ ```
430
+
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.
432
+
433
+ A plugin cannot set the main status line (plugin settings only support `agent` and `subagentStatusLine`), so add it to `~/.claude/settings.json` yourself:
434
+
435
+ ```json
436
+ {
437
+ "statusLine": {
438
+ "type": "command",
439
+ "command": "bulkhead-statusline",
440
+ "refreshInterval": 10
441
+ }
442
+ }
443
+ ```
444
+
445
+ This needs `npm install -g claude-bulkhead`, which puts `bulkhead-statusline` on `PATH`. If you already have a status line script, call `bulkhead-statusline` from it and append its output.
446
+
447
+ ## FAQ
448
+
449
+ **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.
451
+
452
+ **It blocked something it should not have. What do I do?**
453
+ 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.
454
+
455
+ **Is it useful with a single session?**
456
+ 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
+
458
+ **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.
460
+
461
+ **Does it send anything anywhere?**
462
+ 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.
463
+
464
+ More questions, including how the guards can be bypassed and how the RAM numbers are measured, are in [docs/faq.md](docs/faq.md).
465
+
466
+ ## Limitations
467
+
468
+ - The guards read command text. They split it into commands and open wrappers such as `bash -c`, `cmd /c`, `powershell -EncodedCommand` and `sudo` up to three levels deep, but a kill or a sleep hidden in a script file, a shell function or an alias is not seen. They catch the common shapes, not a determined workaround.
469
+ - memory-guard and bulkhead-slot only see RAM. They do not limit CPU, disk or GPU.
470
+ - bulkhead-slot is only used when called. Nothing forces every session to queue through it.
471
+ - Each guard page lists its own limits.
472
+
473
+ ## Uninstall
474
+
475
+ ```
476
+ /plugin uninstall bulkhead@bulkhead
477
+ /plugin marketplace remove bulkhead
478
+ ```
479
+
480
+ 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
+
482
+ ## Using bulkhead? Show it
483
+
484
+ Add this badge to your project's README:
485
+
486
+ ```markdown
487
+ [![Guarded by bulkhead](https://img.shields.io/badge/guarded_by-bulkhead-34d6b4?style=flat-square)](https://github.com/dooly6276/claude-bulkhead)
488
+ ```
489
+
490
+ It renders as [![Guarded by bulkhead](https://img.shields.io/badge/guarded_by-bulkhead-34d6b4?style=flat-square)](https://github.com/dooly6276/claude-bulkhead)
491
+
492
+ ## Contributing
493
+
494
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Security reports: [SECURITY.md](SECURITY.md). The changelog is in [CHANGELOG.md](CHANGELOG.md), and what bulkhead stores and sends is in [PRIVACY.md](PRIVACY.md).
495
+
496
+ ## License
497
+
498
+ [MIT](LICENSE)