never your branch
A fix branch is cut from HEAD before any edit, and your branch is checked back out when phantom finishes — success or failure. The fix exists only as a branch to diff, merge, or delete.
Wrap any command. If it dies, a headless Claude Code session diagnoses the bug, writes a failing test, patches it on a separate branch, verifies the fix independently, and leaves a post-mortem. Your branch is never touched.
or install from GitHub — npm i -g github:waazy-w/claude-phantom
A guest order with no customer takes down a Node service. Phantom catches the exit, opens a fix branch, patches it, runs the tests itself, and writes the post-mortem — while you watch.
examples/crash-demo · one iteration · 1m 48s wall clock
stdout, stderr and stdin stream through byte-for-byte and your exit code is preserved. Phantom keeps the last 256 KiB in a ring buffer and does nothing else — until your process exits non-zero or dies from a signal that wasn't your own Ctrl+C.
Your command runs exactly as before. A 256 KiB ring buffer holds the tail of the output; overhead is a child-process spawn and nothing else.
On a crash, phantom extracts the stack trace, the output tail, git state and your package.json. Terminal escapes are stripped so paths survive intact and secrets can't hide mid-token.
Clean tree? Inside a git repo? Is claude on the PATH? Then, and only then, git checkout -b phantom/fix-<slug>-<ts> from HEAD — before a single edit.
A headless claude -p session starts with a minimal tool allowlist, never-touch globs denied, no network and no push path.
The session reproduces the crash as a test before it changes any source, so the fix is pinned by something that failed a moment ago.
Phantom runs your test command itself, outside the session. If it fails, the session is resumed with the real output — bounded by maxIterations and maxMinutes.
The branch is audited against the starting commit. Any never-touch hit discards the work. Then a commit on the fix branch, a post-mortem, and you're back on your branch.
.phantom/reports/*.mdNothing here trusts the agent's own word. Phantom runs your tests itself and audits the branch after the session ends.
Phantom is built on the assumption that the session will eventually do something wrong. Every rail below is enforced mechanically, not requested in a prompt.
A fix branch is cut from HEAD before any edit, and your branch is checked back out when phantom finishes — success or failure. The fix exists only as a branch to diff, merge, or delete.
git push is a denied tool, there is no network tool, and phantom has no push code path. Not configurable.
An explicit allowlist: read, edit, grep, glob, your test command, node, and read-only git. Everything else is denied without prompting.
A zero-dependency PreToolUse hook that fails closed, inspecting every call for never-touch paths, destructive shell, installs, migrations and state-changing git.
.env, keys, PEM files and secrets are permission deny rules, checked again by the guard hook, then audited afterwards. Any hit hard-reverts the branch.
--setting-sources project,local: your user hooks, permission allows, plugins and their MCP servers are never loaded into the recovery session.
Kills the process tree, resets and cleans the fix branch, checks out your branch, pops the snapshot stash, exits 130.
maxIterations (3) bounds invocations; maxMinutes (15) is a wall-clock timer that kills the child.
PHANTOM_DISABLED=1 turns phantom into a pure passthrough. No flag to remove, no wrapper to unpick.
The session may run node — it has to, to run your tests — and a node -e one-liner can in principle read any file your user can. The guard is lexical. Branch isolation, the post-session audit and the no-push rule are the real backstops. Need hard isolation? Run phantom in a container.
The output tail is scrubbed before the session sees it: KEY=value with secret-looking names, Authorization headers, sk-/ghp_/AKIA/xox tokens, JWTs, URL credentials and PEM blocks. It is pattern-based — a safety net, not a guarantee.
Treat the branch like a PR from a fast contributor who has never seen your codebase: read the report and the diff, run the tests yourself, then merge or delete.
phantom: ✅ FIXED on phantom/fix-typeerror-…-k3f9a2 report .phantom/reports/20260820-184107-…md review git diff main..phantom/fix-…-k3f9a2 merge git merge phantom/fix-…-k3f9a2 discard git branch -D phantom/fix-…-k3f9a2 session 192389b5-e0e9-4c66-a16c-a1a9d4f1cd4b
Exit code is always your command's. A fixed crash is still exit 1, so phantom is safe in scripts and && chains.
# Post-mortem: TypeError: Cannot read # properties of undefined (reading 'email') Status: ✅ FIXED Iterations: 1/3 1m 48s ## Root cause formatOrderLine in src/report.js:9 dereferences order.customer.email unconditionally; a guest checkout in data/orders.json has no customer. ## Fix - const email = order.customer.email; + const email = order.customer?.email ?? '(guest)'; ## Verification (independent) Reproduce (pre-fix) npm test ❌ 1 failed Verify (post-fix) npm test ✅ 5 passed Original command npm start ✅ exit 0 Never-touch audit: clean
The verification table and metadata are written by phantom, not the session.
phantom [flags] [--] <command> [args...] — everything after the command passes through verbatim, so phantom npm run dev --verbose gives --verbose to npm.
| Flag | Effect |
|---|---|
| --dry-run | Diagnose and propose a diff; no branch, no edits. The CI-safe mode. |
| --allow-dirty | Proceed with uncommitted changes after taking a stash snapshot. |
| --test <cmd> | Verification command, overriding config and package.json. |
| --max-iterations <n> | Cap on Claude invocations. Default 3, max 10. |
| --max-minutes <n> | Wall-clock cap for the recovery. Default 15, max 120. |
| --model <m> | Passed through as claude --model <m>. |
| --no-commit | Leave the fix uncommitted on the phantom branch and print the way back. |
| --notify | Desktop notification on crash and when recovery ends. |
| --verbose | Stream the session's progress lines. |
{
"testCommand": "npm test",
"maxIterations": 3,
"maxMinutes": 15,
"neverTouch": [".env", ".env.*", "**/*.pem",
"**/*.key", "**/secrets/**",
"**/*.secret*"],
"webhook": null,
"notify": false,
"model": null,
"autoCommit": true,
"reportDir": ".phantom/reports",
"ringBufferBytes": 262144,
"claudeBin": "claude"
}
| your code | Always. A fixed crash is still exit 1. |
| 128 + sig | Signal deaths, like a shell. SIGSEGV → 139. |
| 130 | Ctrl+C during recovery. |
| 2 | Invalid flags or config — before your command runs. |
Config precedence: flags > .phantomrc > package.json > defaults.
Three optional bridges, all reading .phantom/events.jsonl — git-excluded, capped at 200 lines, events older than 24 h ignored, nothing sent anywhere.
Claude opens your next reply with 👻 phantom: npm run dev crashed 3m ago — fixed on phantom/fix-…, offers the diff and the report, then carries on with what you asked.
on your next message
👻 fixing npm run dev… then 👻 fixed → phantom/fix-… in the status bar until seen.
next redraw
Crash detected, then fixed — with the branch name. macOS, Linux, best-effort, 4 s timeout, never delays a recovery.
instantly
# inside Claude Code /plugin marketplace add waazy-w/claude-phantom /plugin install phantom@claude-phantom # ~/.claude/settings.json — 👻 in the status bar { "statusLine": { "type": "command", "command": "phantom-status" } }
Claude Code can't be interrupted from outside, so the chat message always lands on your next turn. Use the status line or a notification if you want to know instantly.
Straight from the README, because finding this out at 2am is worse than reading it now.
nodemon, pm2, --watch — aren't detected. Wrap the underlying command instead.unfixed or timeout, you're back on your branch, and the report says what was tried.node.FORCE_COLOR=1 or equivalent to keep colours..env and .env.* are never-touch by default, enforced as permission deny rules, by the guard hook on every call, and by a post-session audit that hard-reverts the branch on any hit. Watch your own log output though — the redactor is pattern-based.--allow-dirty stashes a snapshot first and restores it automatically, including on Ctrl+C.maxIterations and maxMinutes — roughly a short interactive debugging session. maxIterations: 1 and a cheaper model give you a hard ceiling.--dry-run: diagnosis and a proposed diff land in .phantom/reports/, with no branch and no edits. Upload .phantom/ as an artifact. Full mode works too, but the branch dies with the runner since nothing is pushed.CLAUDECODE from the environment before spawning the headless session, so nesting works.package.json name and scripts, your test output, and — like any CLI — your environment variables. It can never read or write a never-touch file, push, open a PR, use the network, change branches, install packages, or commit to your branch.You need Node 18+, git, and the Claude Code CLI logged in once. That's the whole setup.
…then pick how you want phantom itself.
The published release. Zero dependencies, so it's a single fast install.
Straight from main — ahead of npm, and how you get a fix before it ships.
For hacking on it. npm link puts phantom on your PATH from the working tree; 193 tests, no install step.
PHANTOM_DISABLED=1 turns it back into a pure passthrough.