anthropics / anthropics/claude-code
[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)
- Ngôn ngữ chính
- Python
- Star
- 145k
- Fork
- 23.1k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
> **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 && 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 )` 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
```shell
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
```
2. 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"
```
3. 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.
4. 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.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Đánh giá
Issue này chưa được đánh giá.