By event
The event decides how much a hook can do. PreToolUse runs before every tool call and can rewrite or block it; SessionStart and Stop run without any action from the user.
| Event | Hooks | High | Worth knowing |
|---|---|---|---|
PostToolUse | 27 | – | 8 |
PreToolUse | 25 | – | 7 |
SessionStart | 16 | – | 10 |
Stop | 10 | – | 8 |
UserPromptSubmit | 9 | – | 7 |
SessionEnd | 6 | – | 5 |
PreCompact | 4 | – | 2 |
SubagentStop | 3 | – | 1 |
SubagentStart | 2 | – | 1 |
PermissionRequest | 2 | – | 2 |
Notification | 2 | – | 1 |
StopFailure | 1 | – | – |
ConfigChange | 1 | – | – |
PostToolUseFailure | 1 | – | – |
FileChanged | 1 | – | – |
PostCompact | 1 | – | 1 |
Setup | 1 | – | – |
WorktreeCreate | 1 | – | – |
What the reviewer flagged
| Finding | Hooks |
|---|---|
unreviewed-script | 42 |
unconditional | 28 |
telemetry | 10 |
broad-matcher | 10 |
credential-access | 2 |
remote-exec | 2 |
Worth knowing — 51
| Hook | Event | Stars | Verdict |
|---|---|---|---|
| SessionStart openclaw/openclaw | SessionStart | 394k | SessionStart (startup) runs an inline bash command that cds to the git root and, if pnpm and pnpm-lock.yaml exist, runs 'CI=true pnpm install --frozen-lockfile --ignore-scripts'. A dependency install from the npm registry on every fresh session with lifecycle scripts disabled; nothing is sent anywhere. |
| PostToolUse n8n-io/n8n | PostToolUse | 210k | PostToolUse on Skill runs .claude/plugins/n8n/scripts/track-skill-usage.mjs, which for n8n-namespaced skills POSTs the skill name and a SHA-256 of username, hostname, OS, arch and release to telemetry.n8n.io using an embedded write key. Telemetry to the repository's own vendor; no file contents, prompts or environment variables are sent. |
| SessionStart google-gemini/gemini-cli | SessionStart | 109k | SessionStart runs 'node ${extensionPath}/scripts/on-start.js' from an example extension under gemini-cli's packages/cli/src/commands/extensions/examples directory; the script could not be resolved (404), so it is unreviewed. This is example material shipped for documentation, not a hook meant for installation. |
| PostToolUse pbakaus/impeccable | PostToolUse | 104k | PostToolUse on Edit|Write runs '.claude/skills/impeccable/scripts/impeccable hook' only if that file exists; the file is a skill-installed binary or script that is not in the packet, so its behaviour is unreviewed. It is described as 'Checking UI changes' with a 5 second timeout. |
| Stop pbakaus/impeccable | Stop | 104k | Stop runs '.claude/skills/impeccable/scripts/impeccable hook' only if that file exists; the file is a skill-installed binary or script that is not in the packet, so its behaviour is unreviewed. It is described as a 'Design deep pass' with a 30 second timeout. |
| PostToolUse ruvnet/RuView | PostToolUse | 103k | PostToolUse on Write|Edit|MultiEdit runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed). The post-edit handler only records an edit metric and the edited path through session.js and intelligence.cjs, which live in the project but were not fetched. |
| PreCompact ruvnet/RuView | PreCompact | 103k | PreCompact (manual and auto) runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed) twice: compact-manual/compact-auto are not defined handlers and just echo '[OK] Hook', then session-end calls intelligence.consolidate and session.end. The visible code is local bookkeeping; the modules doing the work are unreviewed. |
| PreToolUse ruvnet/RuView | PreToolUse | 103k | PreToolUse on Bash runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed). The pre-bash handler itself only string-matches four patterns like 'rm -rf /' and reads the command from the PROMPT/TOOL_INPUT_command env vars rather than stdin, so under Claude Code it checks an empty string and blocks nothing. |
| SessionEnd ruvnet/RuView | SessionEnd | 103k | SessionEnd runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed) with session-end, which calls intelligence.consolidate and session.end to persist session state. The visible code writes nothing outside the project and makes no network calls; the modules doing the work are unreviewed. |
| SessionStart ruvnet/RuView | SessionStart | 103k | SessionStart runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed) for session-restore, then .claude/helpers/auto-memory-hook.mjs import, which loads the @claude-flow/memory package (not in the packet) to pull Claude Code auto-memory files into .claude-flow/data/auto-memory-store.json. Nothing observed leaves the machine, but the real behaviour lives in code the packet does not contain. |
| Stop ruvnet/RuView | Stop | 103k | Stop runs .claude/helpers/auto-memory-hook.mjs sync, which loads the @claude-flow/memory package (not in the packet) and calls syncToAutoMemory and curateIndex, writing insights and a curated index back into the user's Claude Code auto-memory MEMORY.md outside the repository. No network call in the script itself; it silently no-ops when the package is absent. |
| SubagentStart ruvnet/RuView | SubagentStart | 103k | SubagentStart runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed) with 'status', which is not a defined handler and only prints '[OK] Hook: status'. The only real effect is the top-level require of the four unreviewed sibling modules. |
| UserPromptSubmit ruvnet/RuView | UserPromptSubmit | 103k | UserPromptSubmit runs .claude/helpers/hook-handler.cjs, which on every run requires the sibling modules router.js, session.js, memory.js and intelligence.cjs (their top-level CLI code runs on require, and none of them are in the packet, so what actually executes is unreviewed). The route handler prints intelligence context plus a largely hard-coded routing table into the conversation on every prompt; the prompt is read from env, not stdin. |
| SessionStart cline/cline | SessionStart | 71k | SessionStart runs .claude/hooks/claude-code-for-web-setup.sh, which exits unless CLAUDE_CODE_REMOTE=true and otherwise curls the latest gh release tarball from github.com/cli/cli (unpinned, no checksum), 'sudo mv's the binary into /usr/local/bin, then runs 'bun run install:all' and 'bun run protos'. GITHUB_TOKEN is only checked for presence and printed about, never sent; on a local machine the hook does nothing. |
| SessionEnd code-yeongyu/oh-my-openagent | SessionStartSessionEnd | 71k | SessionStart (startup) runs script/agent/setup.sh, which sources the gitignored .env with 'set -a' (exporting any keys into the environment of what follows), then runs 'bun install --ignore-scripts', 'git submodule update --init --recursive', node packages/shared-skills/scripts/materialize-frontend-refs.mjs and 'bun run build' when dist/index.js is missing; the last two are repo scripts not in the packet. SessionEnd runs script/agent/cleanup-hook.sh, which nohups script/agent/cleanup.sh (also not in the packet) with its log in /tmp; nothing observed is sent off the machine. |
| SessionStart code-yeongyu/oh-my-openagent | SessionStartSessionEnd | 71k | SessionStart (startup) runs script/agent/setup.sh, which sources the gitignored .env with 'set -a' (exporting any keys into the environment of what follows), then runs 'bun install --ignore-scripts', 'git submodule update --init --recursive', node packages/shared-skills/scripts/materialize-frontend-refs.mjs and 'bun run build' when dist/index.js is missing; the last two are repo scripts not in the packet. SessionEnd runs script/agent/cleanup-hook.sh, which nohups script/agent/cleanup.sh (also not in the packet) with its log in /tmp; nothing observed is sent off the machine. |
| SessionStart TryGhost/Ghost | SessionStart | 56k | SessionStart runs .claude/hooks/nvm-path.sh on every session, which sources ~/.nvm/nvm.sh and appends one export PATH line to CLAUDE_ENV_FILE so the repo's Node version is on PATH for every later Bash command. Exits silently when nvm is absent; local only, but it changes the session environment. |
| Stop Homebrew/brew | Stop | 50k | Stop runs ./bin/brew lgtm on every stop with a six-minute timeout; bin/brew is Homebrew's own launcher, which reads brew.env config files, strips the environment to an allowlist, and execs Library/Homebrew/brew.sh with the lgtm dev command. The launcher itself is clean, but brew.sh and the lgtm command are not in the packet, so what actually runs on every Stop is unread. |
| PostCompact gastownhall/beads | PostCompact | 29k | PostCompact runs the repo's own `bd codex-hook PostCompact` from the Beads plugin, and the bd binary's behaviour is not in the packet. Beads is a git-backed local issue tracker, but the hook code is unread. |
| PreCompact gastownhall/beads | PreCompact | 29k | PreCompact runs the repo's own `bd codex-hook PreCompact` from the Beads plugin, and the bd binary's behaviour is not in the packet. Beads is a git-backed local issue tracker, but the hook code is unread. |
| SessionStart gastownhall/beads | SessionStart | 29k | SessionStart runs bd codex-hook SessionStart, the beads CLI binary, on every startup/resume/clear to load issue-tracker context. The binary is not in the packet, so it is unreviewed. |
| UserPromptSubmit gastownhall/beads | UserPromptSubmit | 29k | UserPromptSubmit runs bd codex-hook UserPromptSubmit, the beads CLI binary, on every prompt to refresh issue-tracker context. The binary is not in the packet, so it is unreviewed. |
| PreToolUse promptfoo/promptfoo | PreToolUse | 27k | PreToolUse on Bash runs the npm-installed binary node_modules/.bin/block-no-verify, which by its name blocks git commands that pass --no-verify. The binary is not in the repository, so its actual behaviour could not be read and it stays unreviewed. |
| Notification coleam00/Archon | Notification | 24k | Notification runs `kild agent-status --self waiting`, marking the session as waiting in the kild agent manager, with errors ignored. The kild binary is not in the repository, so it is unreviewed. |
| Stop coleam00/Archon | Stop | 24k | Stop runs kild agent-status --self idle on every turn end, a CLI binary not in the packet that reports agent status to the kild tool. Errors are swallowed; the binary is unreviewed. |
| SubagentStop coleam00/Archon | SubagentStop | 24k | SubagentStop for rulecheck-agent runs .claude/skills/rulecheck/hooks/slack-notify.sh, which posts the subagent's last message (first 20 lines) to the Slack webhook in SLACK_WEBHOOK_URL. Nothing is sent when that variable is unset, and the header comment's hard-coded fallback URL does not exist in the code. |
| UserPromptSubmit coleam00/Archon | UserPromptSubmit | 24k | UserPromptSubmit runs kild agent-status --self working on every prompt, a CLI binary not in the packet that reports agent status to the kild tool. Errors are swallowed; the binary is unreviewed. |
| UserPromptSubmit appium/appium | UserPromptSubmit | 22k | UserPromptSubmit runs codegraph prompt-hook on every prompt, a third-party code-indexing CLI that is not in the packet. Unreviewed. |
| PermissionRequest danielmiessler/LifeOS | PermissionRequest | 20k | PermissionRequest on Write|Edit|MultiEdit|Bash and mcp__.* runs Safety.hook.ts, which auto-approves the tool call (emits decision allow) whenever lib/safety-classifier judges it safe and stays neutral otherwise. That classifier is not in the packet, so the set of commands approved without a prompt is unreviewed. |
| PostToolUse danielmiessler/LifeOS | PostToolUse | 20k | PostToolUse runs 13 LifeOS hooks from ~/.claude/hooks, including catch-all hooks (EventLogger.hook.ts, PostToolObserver.hook.ts, LoopDetector.hook.ts) of which EventLogger writes tool inputs, edit diffs and Bash output previews to ~/.claude/LIFEOS/MEMORY. Two go further: ConfigEvalFire.hook.ts spawns a behavioural eval on your Anthropic subscription whenever CLAUDE.md or a .hook.ts is edited (5-minute debounce), and CheckpointPerISC.hook.ts runs `git commit --no-verify` in repos you opt in via ~/.claude/checkpoint-repos.txt (empty by default). |
| PreToolUse danielmiessler/LifeOS | PreToolUse | 20k | LifeOS installs these under ~/.claude/hooks: ContextReduction.hook.sh rewrites every git/gh Bash command to run through the rtk wrapper via updatedInput, PreToolGuard.hook.ts routes Bash/Write/Edit through seven guard modules not included in the packet, AgentInvocation.hook.ts logs Agent dispatches (200-char prompt preview) to ~/.claude/LIFEOS/MEMORY, and TabState.hook.ts sets kitty tab titles; the definition also posts Skill and Agent payloads to http hooks on localhost:31337. Everything reviewed is local; no external network or credential access. |
| SessionEnd danielmiessler/LifeOS | SessionEnd | 20k | SessionEnd runs six LifeOS hooks from ~/.claude/hooks: WorkCompletionLearning.hook.ts and SessionCleanup.hook.ts update work state under ~/.claude/LIFEOS/MEMORY, MemoryHealthGate.hook.ts runs a local check, and UpdateCounts.hook.ts refreshes a usage cache from the Anthropic API. UpdateCounts, DocIntegrity.hook.ts and IntegrityCheck.hook.ts are thin wrappers over handler modules not in the packet, so those parts are unreviewed. |
| UserPromptSubmit danielmiessler/LifeOS | UserPromptSubmit | 20k | Nine LifeOS hooks under ~/.claude/hooks run on every prompt: PromptProcessing.hook.ts sends the prompt plus recent transcript turns to a Haiku inference helper to name the session, ReminderRouter.hook.ts runs gh issue create with the verbatim prompt in the configured WORK.REPO when a 'remind me to' phrase matches, VersionDrift.hook.ts runs git in ~/.claude, PromptProcessing also appends a custom-title record to the session's own .jsonl under ~/.claude/projects, and the others write state under ~/.claude/LIFEOS and inject context. PromptProcessing is truncated in the packet and every hook impor |
| PreToolUse spree/spree | PreToolUse | 16k | PreToolUse on Bash runs .claude/hooks/pre-commit-gate.sh, which inspects every shell command, blocks bare full test-suite runs (rspec, parallel_rspec, pnpm test:e2e), and on git commit or push runs pnpm biome check and pnpm turbo typecheck, exiting 2 to block when they fail. All local project tooling with no network, but it intercepts every Bash call and can block commits, which is worth knowing before installing. |
| Stop fathah/hermes-desktop | Stop | 15k | Stop runs lat hook claude Stop on every turn end, a CLI binary that is not in the packet. Unreviewed. |
| UserPromptSubmit fathah/hermes-desktop | UserPromptSubmit | 15k | UserPromptSubmit runs the external `lat` CLI on every prompt. The binary is not in the repository, so what it does with the prompt is unreviewed. |
| PermissionRequest backnotprop/plannotator | PermissionRequest | 9.9k | PermissionRequest on ExitPlanMode runs the repo's own `plannotator` binary, which opens a plan-review UI and waits up to four days for a decision. The binary is not in the packet, so it is unreviewed. |
| PreToolUse backnotprop/plannotator | PreToolUse | 9.9k | PreToolUse on EnterPlanMode runs plannotator improve-context, the repo's own CLI binary, which is not in the packet. Narrow trigger, unreviewed code. |
| PostToolUse mikro-orm/mikro-orm | PostToolUse | 9.3k | PostToolUse on Write|Edit runs the npm-installed binaries node_modules/.bin/oxfmt (--write) and node_modules/.bin/oxlint (--fix) on the edited .ts/.tsx/.js file, with errors discarded. Both are the oxc project's formatter and linter, but the installed binaries are not in the repository and could not be read, so they stay unreviewed. |
| PreToolUse kunchenguid/firstmate | PreToolUse | 8.6k | PreToolUse runs bin/fm-arm-pretool-check.sh and bin/fm-cd-pretool-check.sh on Bash, which delegate deny decisions to bin/fm-*-command-policy.mjs (not in the packet), and bin/fm-subagent-pretool-check.sh on matcher .* which denies any delegation-shaped tool (Agent, Task, cron, worktree...) inside a firstmate primary checkout. All fail open and only deny; the actual policy logic is unreviewed. |
| Stop kunchenguid/firstmate | Stop | 8.6k | Stop runs bin/fm-turnend-guard.sh, which exits 2 to block the turn from ending whenever a firstmate primary checkout has tasks in flight and no live watcher, and bin/fm-claude-stop-autoarm.sh as an asyncRewake hook with an 8-hour timeout that runs bin/fm-watch-arm.sh (not in the packet) to start the watcher and exits 2 to wake Claude again. Both are inert outside a checkout with AGENTS.md, bin/ and state/, but inside one they keep the session running and re-waking without user input. |
| PostToolUse Azure/azure-sdk-for-net | PostToolUse | 6.1k | PostToolUse on every tool runs eng/common/scripts/azsdk_tool_telemetry.ps1, which on skill invocations launches the locally installed azsdk CLI with ingest-telemetry, sending client type, session id and skill name to Microsoft's Azure SDK telemetry, with no file contents or environment. The AZSDKTOOLS_COLLECT_TELEMETRY=false opt-out is ineffective because the script calls Write-Success before that function is defined and SilentlyContinue swallows the error, so execution falls through and still reports. |
| PostToolUse entireio/cli | PostToolUse | 5.3k | PostToolUse on Agent and TaskCreate|TaskUpdate runs the repo's own `entire hooks claude-code post-task` / `post-todo`, skipped when the `entire` binary is absent. The CLI checkpoints agent sessions into git; its Go code is not in the packet, so it is unreviewed. |
| SessionStart entireio/cli | SessionStart | 5.3k | SessionStart runs .claude/scripts/remote-setup.sh, which only inside Claude Code's web sandbox (CLAUDE_CODE_REMOTE=true) sets DNS to 8.8.8.8 with sudo, installs mise with its official curl | sh installer and writes the environment to CLAUDE_ENV_FILE; a second command runs the Entire CLI's own session-start. Inert on a developer's machine and the installer is a known vendor's, so worth knowing rather than dangerous. |
| Stop facebook/buck2 | Stop | 4.5k | Stop runs arc f on every turn end whenever arc is on PATH; this is assumed to be Meta's arcanist formatter, but the binary is external and not in the packet. Unreviewed. |
| PostToolUse microsoft/skills | PostToolUse | 3.2k | PostToolUse on Task and TodoWrite runs the third-party Entire CLI (`entire hooks claude-code post-task` / `post-todo`), which checkpoints agent sessions into git. The CLI is not in the packet, so it is unreviewed. |
| PreToolUse microsoft/skills | PreToolUse | 3.2k | PreToolUse on Task runs entire hooks claude-code pre-task, the third-party Entire CLI that records agent sessions for the entire.io service. The binary is not in the packet, so what it captures is unreviewed. |
| SessionEnd microsoft/skills | SessionEnd | 3.2k | SessionEnd runs the third-party Entire CLI (`entire hooks claude-code session-end`), which finalises a session checkpoint in git. The CLI is not in the packet, so it is unreviewed. |
| SessionStart microsoft/skills | SessionStart | 3.2k | SessionStart runs entire hooks claude-code session-start on every session, the third-party Entire CLI that records sessions for the entire.io service. The binary is not in the packet, so what it captures is unreviewed. |
| Stop microsoft/skills | Stop | 3.2k | Stop runs entire hooks claude-code stop on every turn end, the third-party Entire CLI that records sessions for the entire.io service. The binary is not in the packet, so what it sends is unreviewed. |
| UserPromptSubmit microsoft/skills | UserPromptSubmit | 3.2k | UserPromptSubmit runs the third-party Entire CLI (`entire hooks claude-code user-prompt-submit`), which checkpoints agent session transcripts into git. The CLI is not in the packet, so its handling of the prompt is unreviewed. |
How it was done
Every standalone hook in the catalogue — a settings.json or hooks.json block with one or more commands — was packed with the scripts its commands name. A script is resolved against the repository the hook came from: $CLAUDE_PROJECT_DIR to the repository root, $CLAUDE_PLUGIN_ROOT to the plugin directory, up to 16 KB read. A script that could not be found, or that lives outside the repository, is recorded as unread and the hook is rated at least "worth knowing" for that reason alone.
Fable 5.1 read each definition and its scripts and answered three things: a risk in three steps, a verdict on every finding of the static scan (many are false), and two sentences for the person about to install it. The verdict is bound to the hash of the definition. A script that changes while the definition does not will not trigger a fresh review; that is a known gap. Files under examples/ or fixtures/ that show an attack are demonstrations and were rated as such.