1. Go to this page and download the library: Download gerard/claude-code-hooks library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
gerard / claude-code-hooks example snippets
use Gerard\ClaudeCodeHooks\Event\HookEventFactory;
use Gerard\ClaudeCodeHooks\Event\Tool\PreToolUseEvent;
use Gerard\ClaudeCodeHooks\Event\Tool\Input\BashInput;
use Gerard\ClaudeCodeHooks\Response\Tool\PreToolUseResponse;
use Gerard\ClaudeCodeHooks\Support\JsonDecoder;
// 1. Decode the inbound payload (always go through JsonDecoder).
$payload = JsonDecoder::decode(file_get_contents('php://input'), 'incoming-hook');
// 2. Resolve to a typed event.
$event = (new HookEventFactory())->fromPayload($payload);
// 3. Pattern-match — read typed properties, decide, respond.
$response = match (true) {
$event instanceof PreToolUseEvent
&& $event->toolInput instanceof BashInput
&& str_starts_with($event->toolInput->command, 'rm -rf /')
=> PreToolUseResponse::deny('blocked: dangerous command'),
default => PreToolUseResponse::allow(),
};
echo json_encode($response->toArray(), JSON_THROW_ON_ERROR);
use Gerard\ClaudeCodeHooks\Event\Tool\PreToolUseEvent;
use Gerard\ClaudeCodeHooks\Event\Tool\Input\BashInput;
use Gerard\ClaudeCodeHooks\Event\Tool\Input\WriteInput;
use Gerard\ClaudeCodeHooks\Response\Tool\PreToolUseResponse;
if ($event instanceof PreToolUseEvent) {
$response = match (true) {
$event->toolInput instanceof BashInput
&& preg_match('#\bsudo\b#', $event->toolInput->command)
=> PreToolUseResponse::deny('sudo blocked by hook policy'),
$event->toolInput instanceof WriteInput
&& str_ends_with($event->toolInput->filePath, '.env')
=> PreToolUseResponse::ask('Confirm before overwriting .env'),
default => PreToolUseResponse::allow(),
};
}
use Gerard\ClaudeCodeHooks\Transcript\TranscriptReader;
use Gerard\ClaudeCodeHooks\Transcript\TranscriptCursor;
use Gerard\ClaudeCodeHooks\Transcript\TranscriptLine;
use Gerard\ClaudeCodeHooks\Transcript\BoundaryEvent;
$reader = new TranscriptReader(); // 8 MB per-line cap by default
$cursor = new TranscriptCursor($path, 0);
foreach ($reader->tail($cursor) as $entry) {
if ($entry instanceof BoundaryEvent) {
// The session was just compacted — relocate your cursor if needed.
continue;
}
/** @var TranscriptLine $entry */
if ($entry->truncated) {
// Line exceeded the 8 MB cap — payload is empty, byte range is preserved.
continue;
}
$entry->type; // "user" | "assistant" | "system" | …
$entry->isSidechain; // true when the line belongs to a subagent fork
$entry->payload; // the full decoded JSON line
}
// `return $reader->tail(...)` yields a TranscriptCursor advanced past the last
// complete line. Persist it to resume later without re-reading the whole file.
use Gerard\ClaudeCodeHooks\Cost\CostCalculator;
use Gerard\ClaudeCodeHooks\Cost\Sonnet45PriceTable;
use Gerard\ClaudeCodeHooks\Transcript\Usage;
$usage = new Usage(
inputTokens: 12_000,
outputTokens: 800,
cacheReadInputTokens: 9_500,
cacheCreationInputTokens: 2_000,
serviceTier: 'standard',
);
$cost = (new CostCalculator())->compute($usage, new Sonnet45PriceTable());
$cost->microDollars; // int — exact, never a float
$cost->asDollars(); // float — for display only
use Gerard\ClaudeCodeHooks\Resolver\HookConfigResolver;
use Gerard\ClaudeCodeHooks\Resolver\FilesystemSettingsLoader;
$resolver = new HookConfigResolver(new FilesystemSettingsLoader([
'policySettings' => '/etc/claude-code/settings.json',
'userSettings' => $_SERVER['HOME'].'/.claude/settings.json',
'projectSettings' => getcwd().'/.claude/settings.json',
'localSettings' => getcwd().'/.claude/settings.local.json',
]));
$registry = $resolver->resolve();
foreach ($registry->rules as $rule) {
$rule->event; // "PreToolUse"
$rule->matcher; // "Bash"
$rule->handler; // ['type' => 'http', 'url' => '…', …]
$rule->source; // HookSource::Project
$rule->managed; // bool — set when the policy enforces `allowManagedHooksOnly`
}
use Gerard\ClaudeCodeHooks\Linter\HookLinter;
use Gerard\ClaudeCodeHooks\Linter\HookConfig;
use Gerard\ClaudeCodeHooks\Linter\ConfigProfile;
use Gerard\ClaudeCodeHooks\Linter\Rule\Cch001BroadMatcherInPolicy;
use Gerard\ClaudeCodeHooks\Linter\Rule\Cch004HttpHandlerWithoutTimeout;
use Gerard\ClaudeCodeHooks\Linter\Rule\Cch005SecretInUrlLiteral;
$linter = new HookLinter([
new Cch001BroadMatcherInPolicy(),
new Cch004HttpHandlerWithoutTimeout(),
new Cch005SecretInUrlLiteral(),
// … or pass every rule under src/Linter/Rule/
]);
$findings = $linter->lint([$hookConfig], new ConfigProfile());
foreach ($findings as $finding) {
$finding->severity; // Severity::Error | Warning | Notice
$finding->code; // "CCH004"
$finding->message; // never embeds a secret value (SEC-08)
$finding->jsonPointer; // "/hooks/PreToolUse/0/hooks/0/timeout"
}
use Gerard\ClaudeCodeHooks\Response\Tool\PreToolUseResponse;
$response = PreToolUseResponse::allow()
->withSuppressOutput(false)
->withSystemMessage('Audited');
echo json_encode($response->toArray(), JSON_THROW_ON_ERROR);
$event->toolName; // "Bash"
$event->toolInput; // BashInput { command: "ls", description: "list", … }
$event->toolResponse; // string|array<string,mixed> — the raw response shape
$event->durationMs; // int|null — null when the wire omits duration_ms
$event->toolUseId; // string|null
$event->reason; // "logout" | "clear" | "other" | …
$event->permissionMode; // PermissionMode::Default when the wire omits it
$event->stopHookActive; // bool — ull
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.