anthropics / anthropics/claude-code
Bash tool kills every command after ~5s ("output file was replaced or could no longer be verified") when an EDR transiently hard-links new files
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
**Version:** 2.1.263 (also reproduced on 2.1.260). macOS 26.3 (Darwin 25.3.0), Apple Silicon. Genian Insights EDR installed (system extension).
## Symptom
Every Bash tool call, foreground or background, sandboxed or with `dangerouslyDisableSandbox`, dies about 5 seconds after start with exit code 137 and:
```
Command killed: its output file was replaced or could no longer be verified
//tasks/.output could not be read (unknown). This usually means another Claude Code process in the same project deleted it during startup cleanup.>
```
Short commands like `pwd` also fail: the command runs and the output file contains the correct output, but the read-back is reported as failed. No other Claude Code process is active in the project. The Read tool reads the same file fine.
## Root cause
The EDR on this machine adds a second hard link to every newly created file on the boot volume for roughly 1 to 8 seconds, then removes it. `stat` on the task output file shows `nlink=2` during that window, then `nlink=1`. Inode, size, owner, mode and content never change. Claude Code's output-file identity check (introduced in 2.1.251, "a sandboxed command cannot redirect or replace them") treats this state as a replaced file, sends SIGTERM then SIGKILL to the shell, and reports the output as unreadable with error code `unknown`.
## Evidence
- Polling `stat -f %l` on any new file (in /tmp, $HOME, ~/.claude, a 0700 dir) shows nlink 1 → 2 → 1 within ~8s. On a separately mounted APFS RAM disk or sparse image, nlink stays 1.
- A trap inside the shell recorded SIGTERM at exactly +5s from the parent `claude` process; the loop kept writing after that until SIGKILL.
- Headless reproduction with `claude -p "run: echo hi && sleep 7 && echo alive" --allowedTools 'Bash(echo:*)'`:
- default tmp dir: killed, 137
- `CLAUDE_CODE_TMPDIR=~/cctmp` (boot volume, 0700): killed, 137
- `CLAUDE_CODE_TMPDIR=/Volumes//t`: works
- `CLAUDE_CODE_TMPDIR=/Volumes//t`: works
## Expected
A transient extra hard link with unchanged dev/inode is not a replaced file. The check should compare device and inode (and possibly owner/mode) rather than failing on `nlink > 1`, or at least retry before killing the command. The error message blaming "another Claude Code process ... startup cleanup" is misleading here.
## Workaround
Point `CLAUDE_CODE_TMPDIR` at a directory on a separately mounted volume (settings.json `env` works).
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce with the provided headless claude -p command while polling stat -f %l on the output file, comparing the default boot-volume tmp directory with the APFS RAM disk and sparse image cases. Trace the Bash tool's output-file identity check introduced in 2.1.251 and verify that unchanged device/inode metadata survives a transient extra hard link without killing the command or misreporting the output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, macos
- Domain
- cli, operating-systems, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100