safe-rm includes a general-purpose hook system for Claude Code that can intercept and require email approval for any tool call -- not just rm commands.
Claude Code supports hooks -- shell commands that run in response to events like tool calls. By writing a hook script that sends events to the safe-rm approval server, you can gate any Claude Code action behind email approval.
This is useful when you let Claude Code run autonomously on a server and want to be notified (and have veto power) when it tries to execute shell commands, write files, or perform other actions.
Claude Code tries to call a tool (e.g., Bash)
│
▼
Hook script intercepts the tool call
│
▼
POST /api/claude-events (HMAC-signed)
│
▼
Server checks auto-approve window
│
┌────┴────┐
│ │
Active No auto-approve
│ │
▼ ▼
Return Create event, send webhook to n8n
immediately │
│ ▼
│ Email with:
│ [Approve] [Deny] [Respond]
│ [Auto-approve 5 min] [Auto-approve 15 min]
│ │
│ ▼
│ Hook script polls /api/claude-events/:id/status
│ │
│ ┌────┴────────┬──────────┐
│ │ │ │
│ Approved Denied Responded
│ │ │ │
▼ ▼ ▼ ▼
exit 0 exit 2 Print response
(proceed) (block) text, exit 0
When a hook event is created, the approval email includes five buttons:
| Button | Action |
|---|---|
| Approve | Approves this single tool call. Claude Code proceeds. |
| Deny | Blocks this single tool call. Claude Code is prevented from executing it. |
| Respond | Opens a form where you can type a custom text response. Claude Code receives this text. |
| Auto-approve 5 min | Approves this call and all subsequent calls from the same session for 5 minutes. |
| Auto-approve 15 min | Same as above, but for 15 minutes. |
When you click an auto-approve button:
- The current event is immediately approved.
- A session-level auto-approve entry is stored with an expiry timestamp.
- All subsequent
POST /api/claude-eventsrequests for the samesession_idreturn200withauto_approved: trueimmediately -- no email is sent, no polling is needed. - After the window expires, new events require email approval again.
The auto-approve window is clamped between 1 and 60 minutes. Expired entries are cleaned up automatically.
Create a script on your server that your Claude Code hook will execute. This script should:
- Read the tool name and input from the hook environment/arguments.
- Construct a JSON payload with an
event_id,event_type,session_id,tool_name, andtool_input. - Sign the payload with HMAC-SHA256 using the shared secret.
- POST to
{SAFE_RM_API}/api/claude-eventswith theX-Claude-Signatureheader. - Poll
{SAFE_RM_API}/api/claude-events/{event_id}/statusuntil resolved. - Exit with code 0 (approved/responded) or code 2 (denied/expired).
Add a hook to your Claude Code configuration (.claude/settings.json or project-level settings) that runs your script on tool calls you want to gate. Refer to the Claude Code documentation for the exact hook configuration format.
The session_id field is used to scope auto-approve windows. Pass a unique identifier for the current Claude Code session so that auto-approve applies to the correct session.
You can set the CLAUDE_SESSION environment variable before starting Claude Code, and have your hook script read it:
export CLAUDE_SESSION="session-$(date +%s)-$$"
claudeYour hook script can then use $CLAUDE_SESSION as the session_id in the event payload.
All Claude Code hook endpoints use the same shared secret (SAFE_RM_SECRET) as the delete approval system, but with the X-Claude-Signature header instead of X-SafeRM-Signature.
| Endpoint | Method | Description |
|---|---|---|
/api/claude-events |
POST | Create an event (requires HMAC signature) |
/api/claude-events/:id/status |
GET | Poll event status |
/api/claude-events/approve/:id/:token |
GET | Approve (email link) |
/api/claude-events/deny/:id/:token |
GET | Deny (email link) |
/api/claude-events/respond/:id/:token |
GET/POST | Custom text response form/submit |
/api/claude-events/auto-approve/:id/:token/:minutes |
GET | Auto-approve for N minutes |
/api/claude-events |
GET | List events (admin, supports ?status= and ?session_id= filters) |
For full request/response schemas, see ARCHITECTURE.md.
| Status | Meaning | Hook script action |
|---|---|---|
pending |
Waiting for user action | Continue polling |
approved |
User clicked Approve (or auto-approved) | Exit 0 (allow) |
denied |
User clicked Deny | Exit 2 (block) |
responded |
User sent a custom text response | Read response_text, exit 0 |
expired |
Timeout reached with no action | Exit 2 (block) |
- The hook system shares the same HMAC secret as the delete approval system. A single
SAFE_RM_SECRETauthenticates both thermclient and hook scripts. - Auto-approve is scoped to a
session_id. Different sessions have independent auto-approve windows. - Auto-approve entries are cleaned up from the database when they expire.
- The custom response form is protected by a separate
response_token(distinct from the approve/deny tokens).