If you found this page, you've likely experienced the moment where Claude Code's interface spins indefinitely after asking it to "start the dev server and check that everything works." The Bash tool goes silent, no error appears, and the session is completely stuck.
I've been building apps independently since 2014 — wallpaper apps, relaxation apps, utility tools — and when I first brought Claude Code into my workflow, this was one of the first walls I hit. Running npm run dev through the Bash tool caused an immediate freeze that I initially mistook for a network issue.
Why Claude Code's Bash Tool Freezes on Long-Running Processes
Claude Code's Bash tool is designed to wait for a command to complete before returning a result. Commands like ls, git status, and npm install work fine because they finish and exit.
But when you pass a foreground process that runs indefinitely — like npm run dev, docker run, or python -m http.server — the Bash tool waits forever for that process to exit. Since a dev server never exits on its own, the session hangs indefinitely.
This is expected behavior, not a bug. The design principle is "run a command, get a result." The problem is that modern web development involves a lot of persistent processes, making this a very common frustration.
Which Commands Trigger This Hang
Frontend / Node.js:
npm run dev,yarn dev,pnpm devnext dev,vite,webpack servenpm start(Express, Fastify, etc.)
Backend and infrastructure:
docker run,docker compose up(without-d)python manage.py runserverrails server,php artisan serve
Watch modes:
jest --watch,vitest watchtsc --watchnodemon server.js
Commands that complete and exit — npm run build, docker build, jest --runInBand — work without issue.
Fix 1 — Background Execution with & and Log Redirection
The most straightforward fix is to run the process in the background.
# Start the dev server in background, save logs to a file
npm run dev > /tmp/dev-server.log 2>&1 &
echo "PID: $!"
# Wait a few seconds then check the log
sleep 3 && cat /tmp/dev-server.logThe & at the end sends the process to the background, allowing the Bash tool to return the PID immediately. Saving $! (the last background PID) lets you stop it later.
Here's how to prompt Claude Code to use this pattern:
Start the development server in the background and confirm it's responding on port 3000.
Run it as: npm run dev > /tmp/dev-server.log 2>&1 &
Then verify with: curl http://localhost:3000
Stop the server after confirming.
Fix 2 — Use timeout for Startup Checks
When you just want to verify the server starts without errors, timeout is useful.
# Run for 10 seconds then stop automatically
timeout 10 npm run dev > /tmp/dev-server.log 2>&1 || true
# Check for meaningful output
grep -E "(error|Error|started|listening|ready)" /tmp/dev-server.logThis is enough for verifying startup errors or confirming the server begins listening.
Fix 3 — Wait for the Server to Be Ready
For a complete startup-and-verify flow, combining background execution with a readiness check is the most reliable pattern.
# Start in background
npm run dev > /tmp/dev-server.log 2>&1 &
DEV_PID=$!
# Poll until the server responds (up to 30 seconds)
for i in $(seq 1 30); do
if curl -s http://localhost:3000 > /dev/null 2>&1; then
echo "✅ Server ready at http://localhost:3000"
break
fi
sleep 1
done
# Stop the server
kill $DEV_PID 2>/dev/nullOr using wait-on if it's available in your project:
npm run dev > /tmp/dev-server.log 2>&1 &
DEV_PID=$!
npx wait-on http://localhost:3000 --timeout 30000 && echo "✅ Server ready"
kill $DEV_PID 2>/dev/nullFix 4 — Docker Always Needs the -d Flag
For Docker, always pass the detach flag when starting containers via Claude Code.
# ❌ This hangs
docker run -p 3000:3000 myapp
# ✅ This works
docker run -d -p 3000:3000 --name myapp myapp
docker logs myappSame applies to docker compose:
# ✅ Background startup
docker compose up -d
# Check status
docker compose ps
docker compose logs --tail=20Prompt Claude Code with explicit instructions:
Start with docker compose up -d and confirm all services are healthy.
Show the output of docker compose ps when done.
Add Process Rules to CLAUDE.md
To avoid repeating this context in every session, add the rules to your project's CLAUDE.md:
## Process Management Rules
- Always run dev servers in the background
- Example: `npm run dev > /tmp/dev.log 2>&1 &`
- Always use `-d` with docker compose
- Verify startup with curl or wait-on
- Stop servers with `kill <PID>` or `docker compose down`
- For tests, use `--runInBand` or single-run mode, never watch modeSee the Claude Code settings and configuration guide for more on encoding environment constraints in your project.
Recovering from a Frozen Session
If the session is already frozen, here's how to recover:
1. Press the stop button in Claude Code (or Ctrl+C in the terminal)
2. Kill any lingering processes manually:
# Kill by port
lsof -ti:3000 | xargs kill 2>/dev/null
# Kill by name
pkill -f "npm run dev"
pkill -f "vite"3. Confirm the port is free:
lsof -i:3000
# No output means the port is availableWatch Mode Tests Also Hang
jest --watch and vitest watch cause the same freeze. Use single-run mode when asking Claude Code to run your test suite.
# ❌ Hangs indefinitely
jest --watch
# ✅ Runs once and exits
jest --runInBand
npx vitest run
# With coverage
jest --coverageWhen prompting Claude Code to verify tests, specify "run tests once" to prevent it from reaching for watch mode.
Common Prompt Patterns
❌ Prone to hanging:
Start the dev server and check that the UI looks correct.
Claude Code will likely run npm run dev directly and freeze.
✅ Hang-safe:
Start the dev server in background (npm run dev > /tmp/dev.log 2>&1 &),
wait until http://localhost:3000 responds with curl,
then report the HTTP status code.
Shut down the server when done.
Once you internalize that the Bash tool is built for "run to completion" commands, you can apply the same background execution pattern to other blocking situations — interactive CLI tools, scripts waiting for stdin, and similar cases.
For broader Bash tool troubleshooting, see the Claude Code Bash tool error guide.