Download the PHP package gerard/claude-code-hooks without Composer
On this page you can find all versions of the php package gerard/claude-code-hooks. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download gerard/claude-code-hooks
More information about gerard/claude-code-hooks
Files in gerard/claude-code-hooks
Package claude-code-hooks
Short Description Type-safe Claude Code hooks for PHP β 29 events, multi-source resolver, transcript reader, linter, doc-drift conformance.
License MIT
Informations about the package claude-code-hooks
π€ What is this?
Claude Code β Anthropic's official CLI for Claude β emits hook events at every interesting moment of a coding session: a tool is about to run, the user submitted a prompt, a session is starting, the context just got compacted, and so on. There are 29 documented events today, each with its own JSON wire shape and decision protocol.
If you want to observe, intercept, redirect, or veto what Claude Code does β from a PHP backend, a CLI, or a Symfony bundle β this package gives you a first-class SDK for it:
- π₯ Decode any inbound hook payload into an immutable typed event. No more
array_key_exists()ladders. - π€ Encode valid response payloads with fluent builders that match the documented decision shapes byte-for-byte.
- π Inspect Claude Code's runtime state: stream transcripts, scan installed plugins / skills / agents / MCP servers, compute Sonnet pricing.
- π‘οΈ Lint your
settings.jsonso misconfigured hooks fail in dev, not in production. - π‘ Stay current β a daily CI job diffs the live Anthropic doc and opens a GitHub issue the moment a new event ships.
It's the layer you'd otherwise have to reinvent in every project that touches Claude Code from PHP.
β¨ Highlights
| π― 29 typed events | Every documented hook (Session, Turn, Tool, Perm, Compact, Ctx, Team) as an immutable DTO. Forward-compatible: unknown event names surface as UnknownHookEvent, never as exceptions. |
| π§° 12 typed tool inputs | Bash, Edit, Write, Read, Glob, Grep, WebFetch, WebSearch, Agent, AskUserQuestion, TodoWrite, Skill. Plus a RawToolInput fallback that handles mcp__<server>__<tool> invocations and any first-party tool not yet modelled. |
| π€ 13 response builders | Immutable, fluent. with*() clones, toArray() ships. Decision shapes match the docs exactly. |
| πͺ Multi-source resolver | Honours the documented precedence: Policy β User β Project β Local β Plugin β Runtime. Filesystem and in-memory loaders included; bring your own by implementing SettingsLoader. |
| π Streaming transcripts | Generator-based JSONL reader with an 8 MB per-line cap (SEC-02), compact_boundary events, and a sidechain grouper. Tail huge transcripts without loading them into memory. |
| π° Cost calculator | Sonnet 4.5 price table out of the box; swap with your own PriceTable. Integer-cent arithmetic via a Money value object β no float drift. |
| π Linter | 6 core rules ship today (broad matchers in policy, unknown events, missing matchers, HTTP handlers without timeout, secret literals, broad matchers on prompt/agent hooks). 18 more on the roadmap (see CHANGELOG.md). |
| π‘οΈ Security-first | realpath-based path-traversal protection (SEC-01), Authorization header redaction (SEC-09), single JsonDecoder chokepoint with JSON_THROW_ON_ERROR, safe-only YAML parsing (SEC-03), 10 MB body cap on the doc-drift fetch (SEC-06). |
| π§ͺ 100 % covered, mutation-tested | PHPStan max + strict, Psalm errorLevel 1, Deptrac layered architecture, MSI β₯ 80 / Covered MSI β₯ 85 β all enforced in CI. |
| π‘ Daily doc-drift | Scheduled CI job re-fetches https://code.claude.com/docs/en/hooks and opens a GitHub issue the moment Anthropic ships a new event. SHA-256 sidecar pins captured fixtures against silent edits. |
π Quick start β 60 seconds
The whole loop is decode β match β respond:
That's it. No manual JSON spelunking, no copy-paste from the docs, no hand-rolled response builders.
π¦ Installation
Requirements
- PHP 8.3+
- Extensions:
ext-json,ext-mbstring
Runtime dependencies
psr/log^3.0symfony/http-client^7.0 (used byAnthropicSpecExtractor)symfony/yaml^7.0 (used by the plugin / skill / agent scanners)
π Cookbook
π― Intercepting a tool call
PreToolUseEvent::toolInput is typed against the ToolInput interface β pattern-match on the concrete class to read the typed properties.
The four documented decisions β allow, deny, ask, defer β are static factories on PreToolUseResponse. Want to mutate the input before letting it through? ->withUpdatedInput($newInput).
π Streaming a transcript
TranscriptReader is a Generator β it never loads the file into memory.
π° Computing usage cost
Money is closed under addition; sum the cost of every assistant turn in a transcript by chaining ->add().
πͺ Resolving multi-source settings.json
The resolver honours the full documented precedence chain and applies the allowManagedHooksOnly policy filter before merging.
π οΈ Building a settings.json fragment
Note: withHeader('Authorization', β¦) only accepts ${VAR} placeholders β this layer never holds real secrets. Resolution happens in the future Symfony bundle.
π Linting a config
π€ Producing a structured response
Every response builder is immutable: with*() returns a clone, toArray() ships.
ποΈ All supported events (29)
| Family | Events |
|---|---|
| Session | SessionStart, SessionEnd, Setup |
| Turn | UserPromptSubmit, UserPromptExpansion, Stop, StopFailure, SubagentStart, SubagentStop |
| Tool | PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch |
| Perm | PermissionRequest, PermissionDenied, Notification, Elicitation, ElicitationResult |
| Compact | PreCompact, PostCompact |
| Ctx | ConfigChange, CwdChanged, FileChanged, InstructionsLoaded, WorktreeCreate, WorktreeRemove |
| Team | TaskCreated, TaskCompleted, TeammateIdle |
Anything Anthropic ships after the pinned snapshot date surfaces as UnknownHookEvent β your code keeps running, the daily doc-drift job opens an issue, the next release adds the typed DTO.
π οΈ All supported tool inputs (12 + fallback)
| DTO | Wire tool_name |
Notable properties |
|---|---|---|
BashInput |
Bash |
command, description, timeout, runInBackground |
ReadInput |
Read |
filePath, offset, limit |
WriteInput |
Write |
filePath, content |
EditInput |
Edit |
filePath, oldString, newString, replaceAll |
GlobInput |
Glob |
pattern, path |
GrepInput |
Grep |
pattern, path, glob, outputMode, caseInsensitive, multiline |
WebFetchInput |
WebFetch |
url, prompt |
WebSearchInput |
WebSearch |
query, allowedDomains, blockedDomains |
AgentInput |
Agent |
prompt, description, subagentType, model |
AskUserQuestionInput |
AskUserQuestion |
questions, answers |
TodoWriteInput |
TodoWrite |
todos[] (content, status, activeForm) |
SkillInput |
Skill |
skill, args (#[\SensitiveParameter]) |
RawToolInput |
everything else | name, payload β covers mcp__* + first-party tools without dedicated DTOs |
π Wire shapes β three quick references
The wire field names below match the actual shape Claude Code emits, verified against a real-corpus sample of 4 089 events.
PostToolUse
SessionEnd
Stop
π‘ One JSON entry point.
JsonDecoder::decode()is the only sanctioned JSON parser in the package. It enforcesJSON_THROW_ON_ERRORand a depth cap of 64. Rawjson_decode()calls insrc/are forbidden by CI.
π‘ Staying in sync with the Anthropic spec
Two CLI binaries ship with the package and back the daily doc-drift.yml workflow:
| Binary | What it does | Exit codes |
|---|---|---|
bin/extract-anthropic-spec |
Fetches https://code.claude.com/docs/en/hooks (or, with --from-html-fixture <path>, parses a captured HTML fixture) and emits a canonicalised JSON spec on stdout or --output <path>. |
0 ok β’ 2 parse failure (NOT drift β the workflow surfaces this as "extractor failure" instead of opening phantom "events removed" issues) |
bin/check-doc-drift |
Diffs the live spec against tests/Fixtures/anthropic-spec/snapshot.json. |
0 no drift β’ 1 drift detected β’ 2 parse error |
Reblessing the snapshot when Anthropic ships a new event:
Then bump AnthropicSpecExtractor::SNAPSHOT_VERSION to the new date. AnthropicSpecExtractorLiveShapeTest asserts the SHA-256 sidecar still matches.
π§ͺ Quality bar
This is a security- and observability-critical package. The bar is set accordingly:
- PHPStan level
max+phpstan-strict-rules+phpstan-deprecation-rules+phpstan-phpunit+ergebnis/phpstan-rulesβ zero baseline. - Psalm
errorLevel: 1+findUnusedCode: true. - Deptrac β strict layered architecture:
Event β Response β Resolver β Transcript β Scanner β Linter β Builder β Conformance β Support. - PHPUnit 11+ with
failOnRisky,failOnWarning, strict coverage metadata. - 100 % class + method coverage enforced via
bin/check-coverage. - Infection β MSI β₯ 80, Covered MSI β₯ 85.
- Composer audit every CI run.
Run the full local gate:
Or the curated bundles:
π Security highlights
| ID | Mitigation |
|---|---|
| SEC-01 | All scanner paths pass through realpath(); symlink escapes from ~/.claude/plugins etc. are rejected. |
| SEC-02 | TranscriptReader caps every line at 8 MB; oversize lines surface as TranscriptLine{truncated: true} instead of OOM-ing. |
| SEC-03 | YAML decoded only via Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE. Object instantiation flags are forbidden (CI grep guard). |
| SEC-04 / 05 | Decoder exceptions never embed the offending payload β only the source path and offset. |
| SEC-06 | AnthropicSpecExtractor HTTP options are pinned: verify_peer/verify_host on, host allow-list, 10 MB body cap. |
| SEC-08 | Linter Finding::$message is capped at 256 bytes and may never embed >8 contiguous chars from the offending input β invariant-tested. |
| SEC-09 | HookConfig::__debugInfo() redacts the Authorization header. |
| SEC-15 | Captured HTML fixtures are CI-scanned for Bearer, eyJ, gha_, glpat-, sk-, xoxb- patterns. |
π€ Contributing
Contributions are welcome β this is a community package and the API is intentionally narrow so it stays reviewable. Please read CONTRIBUTING.md for:
- πͺ the local quality-gate checklist;
- π the 3-step "add an event" workflow;
- πΈ the fixture HTML capture protocol (with the
tests/Fixtures/html/*.sha256sidecar); - π the
#[\SensitiveParameter]convention enforced by the CI grep guard.
Found a bug or a missing event? Open an issue β a failing test fixture in tests/Fixtures/payloads/ is the fastest path to a fix.
See docs/adr/ for architectural decision records.
π License
Released under the MIT License. Copyright Β© 2026 SΓ©bastien Dieunidou and contributors.
All versions of claude-code-hooks with dependencies
ext-json Version *
ext-mbstring Version *
psr/log Version ^3.0
symfony/http-client Version ^7.0
symfony/http-client-contracts Version ^3.0
symfony/yaml Version ^7.0