Skip to content

Commit cc25f7b

Browse files
committed
feat: 🔒️
1 parent f794d98 commit cc25f7b

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

.claude/README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ Claude Code receives instructions from multiple sources, applied in this order:
3939
.claude/
4040
├── README.md # This file
4141
├── CLAUDE.md # Main project instructions
42+
├── settings.json # Project settings (hooks, permissions)
4243
├── commands/ # Slash commands
4344
│ └── new-component.md # /new-component <Name>
45+
├── hooks/ # Custom hook scripts
46+
│ └── read_hook.js # Blocks reading of .env files
4447
└── rules/
4548
├── eds-component.md # EDS 2.0 component conventions
4649
├── figma-component.md # Figma-to-code workflow
@@ -114,6 +117,142 @@ Some workflows use [Model Context Protocol (MCP)](https://modelcontextprotocol.i
114117

115118
MCP servers are configured in Claude Code settings, not in this repository.
116119

120+
## Hooks
121+
122+
Hooks are shell commands that run in response to Claude Code events. They can block actions, validate inputs, or add context. Hooks are configured in `settings.json` and scripts live in `.claude/hooks/`.
123+
124+
### How Hooks Work
125+
126+
1. Claude triggers an event (e.g., about to read a file)
127+
2. Matching hooks receive event data as JSON on **stdin**
128+
3. The hook runs and returns an **exit code**:
129+
130+
| Exit Code | Meaning | Effect |
131+
|-----------|---------|--------|
132+
| `0` | Success | Action proceeds normally |
133+
| `2` | Block | Action is prevented, stderr shown to Claude |
134+
| Other | Non-blocking error | Action proceeds, stderr shown in verbose mode |
135+
136+
### Current Hooks
137+
138+
| Hook | Event | Matcher | Purpose |
139+
|------|-------|---------|---------|
140+
| `read_hook.js` | `PreToolUse` | `Read\|Grep` | Blocks reading `.env` files to protect secrets |
141+
142+
### Configuration
143+
144+
Hooks are defined in `.claude/settings.json`:
145+
146+
```json
147+
{
148+
"hooks": {
149+
"PreToolUse": [
150+
{
151+
"matcher": "Read|Grep",
152+
"hooks": [
153+
{
154+
"type": "command",
155+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/read_hook.js"
156+
}
157+
]
158+
}
159+
]
160+
}
161+
}
162+
```
163+
164+
- **Event** (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, etc.) - when the hook fires
165+
- **Matcher** - regex to filter by tool name (e.g., `Bash`, `Read|Write`, `mcp__.*`)
166+
- **`$CLAUDE_PROJECT_DIR`** - environment variable pointing to the project root, use this for reliable paths
167+
168+
### Available Hook Events
169+
170+
| Event | Fires when | Can block? |
171+
|-------|-----------|------------|
172+
| `PreToolUse` | Before a tool executes | Yes |
173+
| `PostToolUse` | After a tool succeeds | No |
174+
| `PostToolUseFailure` | After a tool fails | No |
175+
| `UserPromptSubmit` | User sends a prompt | Yes |
176+
| `Stop` | Claude finishes responding | Yes (continues) |
177+
| `SessionStart` | Session begins | No |
178+
| `SessionEnd` | Session ends | No |
179+
| `Notification` | Notification is sent | No |
180+
| `PreCompact` | Before context compaction | No |
181+
182+
### Creating a Custom Hook
183+
184+
1. Create a script in `.claude/hooks/` (JS, Bash, Python, etc.)
185+
2. Read JSON from stdin to get event data
186+
3. Return the appropriate exit code
187+
188+
#### Example: Block writing to protected paths
189+
190+
```javascript
191+
// .claude/hooks/protect_paths.js
192+
async function main() {
193+
const chunks = []
194+
for await (const chunk of process.stdin) {
195+
chunks.push(chunk)
196+
}
197+
198+
const { tool_input } = JSON.parse(Buffer.concat(chunks).toString())
199+
const filePath = tool_input?.file_path || ''
200+
201+
const protectedPaths = ['.env', 'package-lock.json', '.github/workflows']
202+
if (protectedPaths.some((p) => filePath.includes(p))) {
203+
console.error(`Blocked: ${filePath} is a protected path`)
204+
process.exit(2)
205+
}
206+
}
207+
208+
main()
209+
```
210+
211+
Then add it to `settings.json`:
212+
213+
```json
214+
{
215+
"hooks": {
216+
"PreToolUse": [
217+
{
218+
"matcher": "Write|Edit",
219+
"hooks": [
220+
{
221+
"type": "command",
222+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect_paths.js"
223+
}
224+
]
225+
}
226+
]
227+
}
228+
}
229+
```
230+
231+
### Hook Input Format
232+
233+
Hooks receive JSON on stdin with these common fields:
234+
235+
```json
236+
{
237+
"session_id": "abc123",
238+
"hook_event_name": "PreToolUse",
239+
"tool_name": "Read",
240+
"tool_input": {
241+
"file_path": "/path/to/file"
242+
}
243+
}
244+
```
245+
246+
The `tool_input` shape depends on the tool being called (e.g., `file_path` for Read, `command` for Bash).
247+
248+
### Tips
249+
250+
- Use `"$CLAUDE_PROJECT_DIR"` (quoted) in commands to handle paths with spaces
251+
- Test hooks by asking Claude to perform the action you want to block
252+
- Use `/hooks` in Claude Code to manage hooks interactively
253+
- Use `.claude/settings.local.json` for personal hooks that shouldn't be shared with the team
254+
- Use `~/.claude/settings.json` for global hooks that apply across all projects
255+
117256
## Comparison with OpenCode
118257
119258
This repository also contains OpenCode configuration in `.opencode/`. Here's how the systems compare:

.claude/hooks/read_hook.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
async function main() {
2+
const chunks = []
3+
for await (const chunk of process.stdin) {
4+
chunks.push(chunk)
5+
}
6+
7+
const toolArgs = JSON.parse(Buffer.concat(chunks).toString())
8+
9+
const readPath =
10+
toolArgs.tool_input?.file_path || toolArgs.tool_input?.path || ''
11+
12+
if (readPath.includes('.env')) {
13+
console.error('You cannot read the .env file')
14+
process.exit(2)
15+
}
16+
}
17+
18+
main()

.claude/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Read|Grep",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/read_hook.js"
10+
}
11+
]
12+
}
13+
]
14+
}
15+
}

0 commit comments

Comments
 (0)