Summary: Claude Code hooks give developers a place to reject a proposed tool call before execution. For a team worried about secret reads or unexpected commands, start at the tool boundary: keep its built-in permissions, then use a PreToolUse hook to check proposed actions against an agent passport and policy. The example below installs APort, permits a harmless command and denies a sensitive file read without executing either tool.
Claude Code documents PreToolUse decision control. A hook can return a permission denial before the proposed tool runs. APort uses that boundary; it does not replace sandboxing, branch protection or least-privilege credentials.
Tested local setup
Prerequisites: macOS or Linux, Bash, Node.js 18 or newer, npm/npx and jq. Network access is needed to download the package. This example was tested with @aporthq/[email protected] on September 30, 2026. It creates a temporary config directory and local passport; no hosted account or API key is needed.
Run the whole block in Bash. The payloads describe proposed tool calls. The test invokes the hook directly, so it does not read a real secret or run a shell tool.
set -euo pipefail
export APORT_CLAUDE_CODE_CONFIG_DIR="$(mktemp -d)"
test -d "$APORT_CLAUDE_CODE_CONFIG_DIR"
export APORT_CONFIG_DIR="$APORT_CLAUDE_CODE_CONFIG_DIR"
export APORT_OWNER_EMAIL="[email protected]"
npx --yes @aporthq/[email protected] claude-code \
--mode=local --non-interactive --enforcement=enforce
# Confirm the installer registered a PreToolUse hook.
jq -e '.hooks.PreToolUse | length > 0' \
"$APORT_CONFIG_DIR/settings.json"
aport_hook="$APORT_CONFIG_DIR/aport/runtime/bin/aport-claude-code-hook.sh"
# A safe proposed command returns no blocking output.
aport_allow=$(printf '%s\n' \
'{"tool_name":"Bash","tool_input":{"command":"pwd"}}' | bash "$aport_hook")
test -z "$aport_allow"
# This path is only an input to policy evaluation, never a real file read.
printf '%s\n' \
'{"tool_name":"Read","tool_input":{"file_path":"/tmp/.env.aport-smoke-test"}}' \
| bash "$aport_hook" > "$APORT_CONFIG_DIR/denied.json"
jq -e '.hookSpecificOutput.permissionDecision == "deny" and
(.hookSpecificOutput.permissionDecisionReason | contains("oap.blocked_pattern"))' \
"$APORT_CONFIG_DIR/denied.json"Expected result: both jq assertions print true, the shell exits successfully, and denied.json contains permissionDecision: deny with reason oap.blocked_pattern. A hook process exiting zero does not mean the tool is allowed: Claude Code reads the structured denial. Inspect the local aport/audit.log under the temporary config directory for the evaluated actions.
Connect the hook to your Claude Code session
The isolated test proves the package's installer and hook behavior. It does not prove your current Claude Code session has loaded that hook.
For your normal setup, open a fresh terminal without the temporary config overrides and run:
npx --yes @aporthq/[email protected] claude-code
npx --yes @aporthq/[email protected] mode claude-code --enforcement=warnChoose a local passport or hosted setup as appropriate. Review the generated settings, restart Claude Code and open /hooks to confirm APort is registered. Create a harmless fixture named .env.aport-smoke-test containing only dummy text. Ask Claude Code to read that fixture and check the warning. Never use a real secret for this test.
In warn mode, a policy denial is recorded but the tool can continue. Switch to enforce only after reviewing your policy, then repeat the fixture test and confirm Claude Code blocks the read. Keep the default permissions and sandbox controls enabled. See the Claude Code security guide for the host's own controls.
What to scope before a team rollout
Review file roots, command rules and MCP server allowlists for the work your developers actually do. The MCP setup example explicitly narrows the default server allowance and proves that an unapproved server is denied.
A local passport is a developer-controlled file. For centralized decisions and audit, configure hosted verification, protect the installed hook from unauthorized changes and confirm every tool path reaches it. Test allowed calls, denied calls and verification failures in the environment you plan to deploy.
The security boundary review covers policy ownership, alternate execution paths and network limits. The decision-versus-enforcement contract explains what report-only findings prove.
Install my guardrail using the Claude Code setup path, then repeat the fixture test in the session that will do the work.
Frequently Asked Questions
Common questions about this topic.