[BUG] @file reference bypasses PreToolUse hooks (e.g. .env read block)

Open 💬 1 comment Opened Jun 29, 2026 by Tomeriko96

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

  1. Create a .env file with sensitive content:

``
SECRET_KEY=super_secret_value
``

  1. 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
```

  1. Make the script executable: chmod +x ~/.claude/hooks/block-env-read.sh
  1. 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"
}
]
}
]
}
}
``

  1. Start a Claude Code session and ask Claude to read the .env file → correctly blocked by the hook with "reading .env files is not allowed".
  1. Start a new prompt and type @.env followed 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 PreToolUse event (e.g. with tool_name: "FileReference") before including @-referenced file contents
  • Add a dedicated hook event like PreFileReference
  • Apply the same hook pipeline that Read would trigger to any @-referenced path

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗