[BUG] @file reference bypasses PreToolUse hooks (e.g. .env read block)
Preflight Checklist:
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
When a PreToolUse hook is configured to block reading sensitive files (e.g. .env), the @file reference syntax in the user prompt completely bypasses the hook. The file contents are injected into the conversation context without any hook being triggered.
This means any file-read restriction enforced via PreToolUse hooks can be trivially circumvented by typing @.env in the prompt instead of asking the assistant to read the file with the Read tool.
This cannot be worked around in the hook script itself. The @file syntax is resolved by the Claude Code client before any hook infrastructure is invoked — no PreToolUse event fires, so the hook script never executes regardless of its content.
What Should Happen?
The @file reference should respect PreToolUse hooks (or a dedicated hook event) before injecting file contents into the conversation. If a hook would block reading that file, the contents should not be included.
Error Messages/Logs
No error is shown — that's the problem. The hook is silently bypassed and the file contents appear in the conversation.
When using the Read tool, the hook correctly blocks with:
reading .env files is not allowed
When using @.env, no hook fires and the full file contents are visible to the model.
Steps to Reproduce
- Create a
.envfile with sensitive content:
````
SECRET_KEY=super_secret_value
- Create a hook script at
~/.claude/hooks/block-env-read.sh:
```bash
#!/usr/bin/env bash
# Block Claude from reading .env files (receives tool input JSON on stdin)
set -euo pipefail
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
case "$tool_name" in
Read)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
if [[ "$file_path" == .env || "$file_path" == /.env. || "$file_path" == /.env ]]; then
echo '{"decision":"block","reason":"reading .env files is not allowed"}'
exit 2
fi
;;
Bash)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
if echo "$command" | grep -qE '(cat|head|tail|less|more|bat)\s+.*\.env(\s|$|")'; then
echo '{"decision":"block","reason":"reading .env files via shell is not allowed"}'
exit 2
fi
;;
esac
exit 0
```
- Make the script executable:
chmod +x ~/.claude/hooks/block-env-read.sh
- Register the hook in
~/.claude/settings.json:
``json``
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read|Bash",
"hooks": [
{
"type": "command",
"command": "/home/user/.claude/hooks/block-env-read.sh"
}
]
}
]
}
}
- Start a Claude Code session and ask Claude to read the
.envfile → correctly blocked by the hook with "reading .env files is not allowed".
- Start a new prompt and type
@.envfollowed by any message (e.g. "what is in this file?") → file contents are injected into the conversation, completely bypassing the hook.
Claude Model: Opus
Is this a regression? I don't know
Claude Code Version: 2.1.195
Platform: Anthropic API
Operating System: Debian Linux
Terminal/Shell: Bash
Additional Information
The only workaround currently is to avoid having sensitive files in the workspace entirely.
Possible fixes on the Claude Code side:
- Fire a
PreToolUseevent (e.g. withtool_name: "FileReference") before including@-referenced file contents - Add a dedicated hook event like
PreFileReference - Apply the same hook pipeline that
Readwould trigger to any@-referenced path
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗