[BUG] Bash tool (Linux): after `exec` in a command, the CLI is the parent of the program, so killing that parent PID stops the session (pkill is guarded, kill is not)

Status Open
Reported on v2.1.272
Maintainer reply None cached
Activity 0 comments · opened Sep 15, 2026
Note: Claude wrote this issue, not the account owner. Claude is the model (Claude Opus 5) in the Claude Code session that stopped. The account owner asked Claude to find the cause and to report it. The analysis and the reproduction are Claude's.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

A command that the Bash tool ran stopped the CLI process (claude), and the session ended. kill with no signal argument sends SIGTERM, so SIGTERM is the probable signal. No exit status was recorded.

The cause is the process tree. The shell that runs a Bash tool command is a direct child of the CLI. This is true for foreground commands and for run_in_background tasks. Each of these shells is in its own session and process group, so a kill of that group does not reach the CLI. If the command uses exec, the new program replaces that shell and keeps its PID. The new program then has the CLI as its parent, and a kill of that parent PID reaches the CLI.

A common cleanup pattern then targets the CLI: find the PID on a port, and kill that PID and its parent.

What happened in the session (Linux, 2.1.272):

  1. A background task started a server with a launcher script: cd <dir> && exec ./bin/kc.sh start-dev ... (Keycloak 26.7.2, the public distribution).
  2. kc.sh starts Java in the background. When Java exits with code 10, kc.sh starts it again with eval exec java .... The server log shows the build step after which Java exits with code 10 on the first start of a new distribution. After the two exec calls, Java would have had the PID of the original shell, with the CLI as its parent. This was not observed directly. The safe reproduction below shows the same parent with one exec.
  3. Later, a foreground Bash command stopped the server with for p in $(lsof -ti :8080); do kill $p $(ps -o ppid= -p $p); done.
  4. The CLI stopped within about one second of the server shutdown in the server log. The account owner resumed with claude --continue. The transcript shows [Request interrupted by user for tool use] for that tool call.

The fix in 2.1.214 ("Fixed Bash tool killing the Claude session when a pkill -f pattern accidentally matched the CLI's own process (Linux)") added a guard to the shell snapshot: function pkill refuses a pattern that matches $CLAUDE_PID. There is no guard for kill with an explicit PID. In this case, $(ps -o ppid= -p <pid>) is equal to $CLAUDE_PID.

Two details made this error likely:

  • lsof -ti :PORT also returns clients of the port, for example a browser that an MCP server controls. lsof -ti tcp:PORT -sTCP:LISTEN returns only the listener.
  • "Kill the wrapper and its parent" is a normal way to remove orphan child processes. The model used it without a check of the parent chain.

What Should Happen?

A Bash tool command must not be able to stop the CLI by accident. Possible fixes:

  1. Guard kill in the shell snapshot in the same way as pkill: refuse a signal to $CLAUDE_PID. This is a partial fix. kill is a bash builtin, so a shell function does not cover command kill, /bin/kill, xargs kill, or a kill inside a script. The guard must also keep kill -0 and kill -l working.
  2. Start the command shell under an intermediate process, so that an exec in the command never gives a program the CLI as its direct parent. This is the structural fix.
  3. At a minimum, tell the model in the Bash tool description that its shell is a child of the CLI. Also tell it that exec in a background task makes the CLI the parent of the program.

Error Messages/Logs

No error message. The CLI process stopped. No exit status was recorded.

On resume (claude --continue), the tool result of the interrupted call was:
[Request interrupted by user for tool use]

Server log at the time of the stop:
INFO  [io.quarkus] (Shutdown thread) Keycloak stopped in 1.066s

Steps to Reproduce

This safe version shows the process tree. It does not stop the CLI.

  1. Ask Claude Code to run this command with run_in_background:

``bash
exec python3 -m http.server 8765 --bind 127.0.0.1
``

  1. Ask Claude Code to run this command in the foreground:

``bash
P=$(lsof -ti tcp:8765 -sTCP:LISTEN)
echo "listener: $(ps -o pid=,comm= -p "$P")"
echo "parent: $(ps -o pid=,comm= -p "$(ps -o ppid= -p "$P")")"
echo "CLAUDE_PID=$CLAUDE_PID"
ps -o pid=,ppid=,pgid=,sid=,comm= -p "$P" "$CLAUDE_PID"
``

  1. Result on 2.1.272 (Linux): the parent of the python3 listener is claude, and its PID is equal to $CLAUDE_PID. The listener is in its own process group and session (PGID and SID are equal to its PID), not in the group of the CLI.
  2. Stop only the listener: kill "$P".

The destructive step is kill "$(ps -o ppid= -p "$P")". Do not run it in a session that you want to keep. Claude did not run this step again in isolation. The evidence for it is the original session above.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.272 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

  • Ubuntu 24.04.5 LTS, x86_64. Claude Code runs in tmux over SSH. The shell is bash. Login is a Claude subscription (OAuth), not an API key.
  • Related, but not the same bug:
  • #93607: pkill -f and pgrep -f match the tool's own bash -c … eval wrapper (exit 144). It also describes the 2.1.214 guard, which covers pkill only. This bug also occurred once in the same session.
  • #16135: killing a background process from the task panel stops Claude Code in Docker. The reporter's analysis is that both share a process group.
  • #3068 (closed): Claude Code stopped itself with pkill node. The 2.1.214 pkill guard addresses that kind of pattern.
  • Not tested on macOS. The pkill guard runs only when /proc/$CLAUDE_PID/comm is readable. macOS has no /proc, so that guard does not run there.
  • The workaround that the account owner now keeps in their CLAUDE.md:
  • Do not exec a long-running launcher in a background task.
  • Kill only the listener: lsof -ti tcp:PORT -sTCP:LISTEN.
  • Never add the parent PID to a kill list. Before a kill, check each parent process up to PID 1, and stop if claude is one of them.

View original on GitHub ↗