Human-in-the-Loop Approvals for Claude Code
Autonomous Agents, Unintended Consequences
AI coding agents have deleted production databases, wiped home directories, and nuked inboxes while their owners frantically typed “STOP”. These aren’t hypotheticals. They’re GitHub issues and news articles from the last year.
Claude Code’s hooks system can intercept dangerous actions before they execute, but approvals are terminal-bound. You need to be at your laptop, watching. That doesn’t scale to long-running autonomous sessions where you’ve stepped away or when they are running remotely.
Free-Text Approvals Don’t Work
One approach to remote approvals is messaging. The agent sends a Slack message describing the action, the human replies, and the agent parses the response.
Consider Claude attempting git push --force origin main:
Agent: "Can I run: git push --force origin main?"
Human: "yeah go ahead but use the feature branch instead"
The agent must now infer:
- Decision: approved (parsed from “yeah go ahead”)
- Branch:
feature-branch?feat/cleanup?dev? (ambiguous) - Flags: keep
--force, or switch to--force-with-lease? (unspecified)
This is AI interpreting safety-critical decisions from natural language, the same class of problem that caused the incidents above.
Introducing @justack/claude-hook
@justack/claude-hook is a PreToolUse hook that routes dangerous tool calls to your Justack inbox for approval. Instead of free-text chat, it uses Justack’s ask() API to present typed, structured inputs:
- Approve/Deny:
ConfirmInput(boolean) - Feedback:
TextInputrelayed to Claude as additional context - Modified arguments:
TextInputreplacing the original tool input
All three inputs are sent in a single ask() call. The recipient sees and responds to them together.
The hook receives structured data back, not a message to parse:
{
"approve": true,
"feedback": "use --force-with-lease instead",
"modified_args": {
"command": "git push --force-with-lease origin feature-branch"
}
}
A boolean for the decision. An exact command string for the modification. No inference required.
How It Works
On every tool call, the hook evaluates the action against a rule engine:
- Match tool name:
Bash,Write,Edit, or glob patterns likemcp__* - Apply rule mode:
"all"(always approve),"none"(auto-approve), or"pattern"(substring matching) - For pattern mode: check the command against dangerous and safe pattern lists
For git push --force origin main:
| Step | Result |
|---|---|
Tool name Bash matches rule Bash | mode: pattern |
Command contains git push | matches dangerous pattern |
| Decision | require approval |
Safe commands like git status, git log, and ls match safe patterns and execute without interruption. Unknown commands default to requiring approval (fail-closed).
When approval is required, the hook sends the full command and working directory to the recipient’s Justack inbox and blocks until a response is received (up to 15 minutes).
All approvals within a Claude Code session appear in a single Justack session thread.
Get Started
1. Create a Justack API key
Sign up at dashboard.justack.dev and create an API key.
2. Initialize the hook
npx @justack/claude-hook init
The wizard prompts for your API key and recipient email, validates credentials, then writes two files:
.justack/config.json, hook configuration with default rules:
{
"apiKey": "jstk_...",
"recipientEmail": "you@example.com",
"rules": {
"Bash": {
"mode": "pattern",
"dangerous": ["rm ", "git push", "sudo ", "npm publish", ...],
"safe": ["ls", "git status", "git diff", "pnpm install", ...]
},
"Write": { "mode": "all" },
"Edit": { "mode": "all" },
"mcp__*": { "mode": "all" }
}
}
.claude/settings.json, registers the PreToolUse hook with Claude Code:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit|mcp__.*",
"hooks": [
{
"type": "command",
"command": "npx @justack/claude-hook approve",
"timeout": 900
}
]
}
]
}
}
The init command also adds .justack/ to .gitignore since the config contains your API key.
3. Use Claude Code normally
Dangerous actions now route to your inbox. Safe actions proceed without interruption.
Configuration
Customize rules in .justack/config.json to match your workflow:
| Mode | Behavior |
|---|---|
"all" | Every call requires approval |
"none" | Auto-approve all calls |
"pattern" | Classify by substring matching; unknown commands require approval |
Tool names support glob patterns. mcp__* matches all MCP tools, mcp__github__* targets a specific server.
Default dangerous patterns: rm, git push, sudo, npm publish, kill, curl -X POST.
Default safe patterns: ls, git status, git diff, pnpm install, tsc, eslint.
See the docs for the full reference.