109 Claude Code hooks, reviewed for what they actually run

A hook is a command an agent runs on an event — before a tool call, when a session starts, when it stops — and nobody reads it first. The catalogue's static scan reads only the JSON that names the script. So each hook was handed to a model with the script it points to, and the verdict is written on the hook's page. This is the sum.

Reviewer: Fable 5.1 · 7 Sept 2026 · 109 of 11,464 standalone hooks

High risk0Exfiltrates, runs remote code, persists outside the project, or destroys. Shown with a red banner on the page.
Worth knowing51Legitimate, but talks to a vendor, edits your settings, runs unconditionally, or names a script the reviewer could not read.
Clean58Formatters, linters, tests, notifications, guards. 53% of what was reviewed.
Scripts read72Hooks whose script was fetched and read alongside the definition; 3 named a script that could not be found and are rated at least "worth knowing" for that reason.

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.

EventHooksHighWorth knowing
PostToolUse27–8
PreToolUse25–7
SessionStart16–10
Stop10–8
UserPromptSubmit9–7
SessionEnd6–5
PreCompact4–2
SubagentStop3–1
SubagentStart2–1
PermissionRequest2–2
Notification2–1
StopFailure1––
ConfigChange1––
PostToolUseFailure1––
FileChanged1––
PostCompact1–1
Setup1––
WorktreeCreate1––

What the reviewer flagged

FindingHooks
unreviewed-script42
unconditional28
telemetry10
broad-matcher10
credential-access2
remote-exec2

Worth knowing — 51

HookEventStarsVerdict
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.

How the security scan and the AI review work · All hooks