本文へ移動
cccskills
無料GitHub で公開

eval-hooks

Audit Claude Code and Codex hooks for validity, safety, and correctness across settings.json, hooks.json, config.toml, plugin, and skill or agent frontmatter hooks. Resolves each command, checks event names, handler types, matchers, exit-code and JSON decision strategy, and timeouts against each host's documented rules, then reviews hooks one by one. Use when setting up hooks, debugging a hook that never fires, never blocks, or hangs the agent, or doing a periodic hooks hygiene pass. Not for writing a new hook from scratch.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md33.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Hooks evaluator

Discover every Claude Code and Codex hook in scope, validate each one against the filesystem and the host's documented hook semantics, then run an interactive session to confirm or improve them.

The goal is not just to score; it is to leave every hook working, correctly scoped, and safe to run.

Sources of truth: the official Claude Code hooks reference (https://code.claude.com/docs/en/hooks) and the Codex hooks documentation (https://developers.openai.com/codex/hooks). When a rule below and the live documentation disagree, the documentation wins; report the disagreement instead of scoring against stale text.

When to use

  • First time adding hooks (validate before committing)
  • A hook never fires, or fires on every tool call
  • The agent hangs noticeably before executing a tool
  • A policy hook is supposed to block but doesn't
  • After copying hooks from another project, machine, or host
  • Periodic hygiene: "are all these hooks still doing something useful?"

Scope

ArgumentAudit
noneEvery Claude Code and Codex location listed below that exists
claude or codexThat host only
a file pathThat file only (settings JSON, hooks.json, config.toml, skill or agent file)
audit-only, or the user asks for no changesSteps 1-4 and 6 only: list each proposed change instead of asking or editing, and omit the user-feedback lines from the report

Audit each host with its own rules. Never score a Codex hook against Claude Code semantics or the reverse.

When a ctxharness doctor --format json report generated during this task is available, reconcile against it: every hook-layer finding it reports must appear in the audit, and every extra finding needs file:line evidence.


Claude Code: Key concepts

Event types (33)

EventWhen it firesExit 2 effect
SessionStartSession begins or resumesStderr to user only
Setup--init-only, or --init / --maintenance in -p modeIgnored
UserPromptSubmitPrompt submitted, before processingBlocks and erases the prompt
UserPromptExpansionA typed command expands into a promptBlocks the expansion
PreToolUseBefore a tool call executesBlocks the tool call
PermissionRequestA tool call needs a permission decisionNot honored: deny through the JSON decision object
PermissionDeniedAuto mode denies a tool callIgnored (only hookSpecificOutput.retry is read)
PostToolUseAfter a tool call succeedsStderr to Claude; the tool already ran
PostToolUseFailureAfter a tool call failsStderr to Claude
PostToolBatchAfter a full batch of parallel tool callsStops the agentic loop
NotificationClaude Code sends a notificationIgnored
MessageDisplayWhile assistant text is displayedOriginal text displayed
SubagentStartA subagent is spawnedStderr to user only
SubagentStopA subagent finishesPrevents the subagent from stopping
TaskCreatedA task is being createdRolls back the creation
TaskCompletedA task is being marked completedPrevents completion
StopClaude finishes respondingPrevents stopping, continues the turn
StopFailureThe turn ends on an API errorIgnored (except terminalSequence)
TeammateIdleAn agent team teammate is about to go idleKeeps the teammate working
InstructionsLoadedA CLAUDE.md or .claude/rules/*.md file loadsIgnored
ConfigChangeA configuration file changes mid-sessionBlocks the change (except policy_settings)
CwdChangedThe working directory changesStderr to user only
DirectoryAddedA directory is added via /add-dir or SDK register_repo_rootStderr to debug log; already added
FileChangedA watched file changes on diskStderr to user only
WorktreeCreateA worktree is being createdAny non-zero exit fails creation
WorktreeRemoveA worktree is being removedAny non-zero exit fails removal if the directory remains
PreCompactBefore compactionBlocks compaction
PostCompactAfter compactionStderr to user only
PreModelSwitchBefore a requested model switchBlocks the switch
PostModelSwitchAfter the session model changesStderr to user only
ElicitationAn MCP server requests user inputDenies the elicitation
ElicitationResultAfter the user answers an elicitationBlocks the response (becomes decline)
SessionEndSession terminatesStderr to user only

Exit codes and JSON output

  • Exit 2 blocks on the events whose row says so. No JSON can override it, not even permissionDecision: "allow".
  • JSON is read on every exit code, not only 0. For events with a standard decision model, a valid JSON decision takes effect on exit 0 or any other non-2 code: hookSpecificOutput.permissionDecision: "deny" (PreToolUse, PreModelSwitch), hookSpecificOutput.decision.behavior: "deny" (PermissionRequest), top-level decision: "block" (UserPromptSubmit, UserPromptExpansion, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, SubagentStop, ConfigChange, PreCompact, TaskCreated, PreModelSwitch).
  • Any other non-zero code without valid JSON is a non-blocking error: the action proceeds and the transcript shows the first stderr line. A missing or non-executable script exits with a code like 127 and lands in this bucket, so a mistyped path silently disables a policy gate.
  • Exit 0 stdout becomes context only on UserPromptSubmit, UserPromptExpansion, SessionStart, and PostModelSwitch; elsewhere it goes to the debug log.

A policy hook is correctly built when it uses either exit 2 or a JSON decision supported by its event. Exit 1 alone is the defect.

Timeout defaults (seconds)

HandlerDefault
command, http, mcp_tool600
same, on UserPromptSubmit, PreModelSwitch, PostModelSwitch30
same, on MessageDisplay10
prompt30
agent60
SessionEnd1.5 total budget; a per-hook timeout raises it to the highest configured value, up to 60. Plugin hook timeouts do not raise it. CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS overrides it

A timed-out command, http, or mcp_tool hook on PreToolUse does not block the tool call; on PreModelSwitch it blocks the switch. timeout is not enforced on a running async: true hook; it is enforced with asyncRewake.

Handler types and where they are allowed

Handler typeAllowed on
command, mcp_toolEvery event
httpEvery event except SessionStart and Setup
promptPermissionDenied, PostToolBatch, PostToolUse, PostToolUseFailure, PreToolUse, Stop, SubagentStop, TaskCompleted, TaskCreated, TeammateIdle, UserPromptExpansion, UserPromptSubmit, PermissionRequest
agent (experimental)Same as prompt, except PermissionRequest, where an agent hook is skipped

On PermissionDenied, prompt and agent hooks run but their output is discarded.

Handler fields

FieldApplies toNotes
typeallcommand, http, mcp_tool, prompt, agent
ifallOne permission rule, such as Bash(git *). Evaluated only on PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied; on any other event the hook never runs. Best-effort filter, not an enforcement mechanism
timeoutallSeconds
statusMessageallSpinner text
onceallHonored only in skill frontmatter; ignored in settings files and agent frontmatter
commandcommandShell string, or the executable when args is set
argscommandExec form: no shell, each element one argument. Preferred when referencing ${CLAUDE_PROJECT_DIR} or ${CLAUDE_PLUGIN_ROOT}
shellcommandbash or powershell; ignored when args is set
asynccommandRuns in the background; cannot block or decide
asyncRewakecommandBackground, and wakes Claude when the process exits 2
url, headers, allowedEnvVarshttpBlocks only through a 2xx JSON body; status codes alone never block
server, tool, inputmcp_toolUses an already connected server
prompt, modelprompt, agent$ARGUMENTS receives the hook input

Matchers

Matcher valueEvaluated as
"*", "", or omittedMatch all
Only letters, digits, _, -, spaces, ,, |Exact string, or a list separated by | or , (hyphens need v2.1.195+)
Any other characterUnanchored JavaScript regex (Edit.* also matches NotebookEdit; anchor with ^...$)

FileChanged and StopFailure use a narrower exact set (letters, digits, _, |). Each event filters a different field:

EventMatcher filters
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDeniedtool name (Bash, Edit|Write, mcp__server__.*)
SessionStartstartup, resume, clear, compact, fork
Setupinit, maintenance
SessionEndclear, resume, logout, prompt_input_exit, other
Notificationnotification type
SubagentStart, SubagentStopagent type
PreCompact, PostCompactmanual, auto
PreModelSwitch, PostModelSwitchcanonical target model name
ConfigChangeconfiguration source
DirectoryAddedslash_command, register_repo_root
FileChangedliteral filenames to watch
StopFailureerror type
InstructionsLoadedload reason
UserPromptExpansioncommand name
Elicitation, ElicitationResultMCP server name

No matcher support (a matcher is silently ignored): UserPromptSubmit, PostToolBatch, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, CwdChanged, MessageDisplay.

Execution model

  • All matching hooks run in parallel.
  • The same handler defined in more than one settings file runs once. A plugin's or a skill's copy of the same handler stays separate.
  • PermissionRequest hooks also run in sessions that cannot show a prompt, such as background subagents in -p mode; if no hook decides, the call is denied.
  • Several PreToolUse decisions resolve as deny > defer > ask > allow. The documentation does not define which updatedInput wins when several hooks rewrite the same call.
  • Stop and SubagentStop continuations, through decision: "block" or additionalContext, are bounded by the stop_hook_active input and an 8-consecutive-continuation cap.
  • Command hooks run without a controlling terminal: interactive commands (read, fzf, gum, any TUI) cannot get input. Use terminalSequence in JSON output for notifications.

Claude Code: Locations scanned

LocationScopeNotes
Managed policy settingsOrganizationCannot be disabled by user, project, or local disableAllHooks
~/.claude/settings.jsonUserNot read by cloud sessions
.claude/settings.jsonProject, committed
.claude/settings.local.jsonProject, personal
Plugin hooks/hooks.jsonWhile the plugin is enabled
Skill frontmatter hooks:Rest of the session once the skill is invokedonce: true honored here only
Subagent frontmatter hooks:While that subagent runsStop becomes SubagentStop. Project subagent hooks run only after workspace trust is accepted, including in -p

~/.claude/settings.local.json is not a documented settings location. If it exists, report it as undocumented and do not score its hooks as active.

Also record, from the effective settings:

  • disableAllHooks: true disables every non-managed hook; only a managed-level value disables managed hooks.
  • allowManagedHooksOnly blocks user, project, local, and plugin hooks (plugins force-enabled in managed enabledPlugins are exempt).
  • allowedHttpHookUrls and httpHookAllowedEnvVars restrict HTTP hooks from every source.
  • Interactive sessions hold back every settings-file hook until workspace trust is accepted; -p and SDK sessions treat the folder as trusted.

Codex: Key concepts

Locations

LocationNotes
~/.codex/hooks.jsonUser
~/.codex/config.toml inline [hooks]User
<repo>/.codex/hooks.json, <repo>/.codex/config.tomlLoaded only when the project .codex/ layer is trusted
Plugin hooks/hooks.json, or a hooks entry in .codex-plugin/plugin.jsonManifest paths must stay inside the plugin root
Managed requirements.toml [hooks]Trusted by policy; allow_managed_hooks_only = true skips user, project, session, and plugin hooks

Every source loads; higher layers do not replace lower ones. A layer holding both hooks.json and inline [hooks] is merged with a startup warning: flag it and recommend one representation per layer. [features] hooks = false turns hooks off (codex_hooks is a deprecated alias).

Trust review

Every non-managed hook, plugin hooks included, must be reviewed and trusted in /hooks before it runs. Trust is recorded against the hook's current hash, so an edited hook is skipped until trusted again. When auditing, report that a changed hook needs review; do not assume it runs. The /hooks browser is the authoritative view of trust. Current Codex builds also persist it under [hooks.state] in config.toml, which is undocumented: report what you observe there without printing hash values, and never treat [hooks.state] as an event. --dangerously-bypass-hook-trust bypasses the check for one invocation only: flag any automation that relies on it.

Check how codex is launched before reading trust state as runtime behavior: type codex and which -a codex. A terminal integration can put a wrapper first on PATH that adds --dangerously-bypass-hook-trust and -c hooks... overrides to every invocation; Codex then lists the flag among its startup warnings. Observed with cmux, whose wrapper skips the injection when CMUX_CODEX_HOOKS_DISABLED=1. Under such a wrapper every hook runs without review: report that trust findings do not describe what runs.

Events (12) and matchers

EventMatcher filters
PreToolUse, PostToolUse, PermissionRequesttool name (Bash, apply_patch, MCP names; Edit or Write also match apply_patch)
SessionStartstartup, resume, clear, compact
SessionEndcurrently only other
PreCompact, PostCompactmanual, auto
SubagentStart, SubagentStopsubagent type
UserPromptSubmit, Stop, Interruptnot supported (matcher ignored)

Codex matchers are regex strings. Interrupt and SessionEnd do not run for subagents.

Handlers and output

  • Only command and mcp_tool handlers run. prompt and agent handlers are parsed and skipped: flag them as dead configuration.
  • timeout is in seconds, default 600. SessionEnd and Interrupt default to 1 and allow at most 3.
  • Optional fields: statusMessage, async, additionalContextLimit (default 2,500 tokens; 0 passes everything and can flood the context), commandWindows / command_windows.
  • Commands run with the session cwd. For repo-local scripts, resolve from the git root rather than a relative .codex/hooks/... path.
  • PreToolUse ignores plain-text stdout. It blocks with permissionDecision: "deny", legacy decision: "block", or exit 2 with the reason on stderr. It rewrites a call with permissionDecision: "allow" plus updatedInput (a string command for Bash and apply_patch, the replacement arguments for MCP tools), the same shape Claude Code uses. permissionDecision: "ask", continue, stopReason, and suppressOutput are not supported there: Codex marks the hook run as failed and continues the tool call. Flag any Codex policy hook that relies on them.
  • Background (async) hooks cannot block, approve, or rewrite; at most eight run concurrently; SessionEnd always runs synchronously.
  • SessionEnd does not support MCP tool hooks.
  • Plugins written for Claude Code can print output Codex rejects. Observed on Codex 0.159.2: a SessionStart hook printing two JSON objects ({"async": true, ...} then a metrics object) fails with hook returned invalid session start JSON output, and a Stop hook printing {"metrics": ...} fails with hook returned invalid stop hook JSON output. Read the hooks file that the plugin's .codex-plugin/plugin.json names, which can differ from hooks/hooks.json (for example hooks/codex-hooks.json), run each plugin hook once with a sample payload for its event, and parse stdout against that event's contract. Recommend disabling the plugin in Codex when several of its events fail.

Scoring Criteria (10 pts per hook)

#CriterionMaxWhat is checked
1event and handler type1The event exists for this host, and the handler type is allowed on it (a Claude agent hook on PermissionRequest or a Codex prompt hook scores 0)
2matcher2No matcher on an event without matcher support, or a matcher using that event's documented values (1); not a match-all or broad regex on a tool event paired with a slow command (1)
3command3Non-empty (1); the script or binary resolves on disk, after substituting ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT}, or the git root (1); the script is executable, or it is passed to an interpreter (bash script.sh, python3 script.py) or run in exec form with a resolvable executable (1)
4timeout2The effective timeout (explicit or documented default) fits the event: interactive-path hooks (PreToolUse, PermissionRequest, UserPromptSubmit, PreModelSwitch, Stop) under 30 s or backed by an internal timeout guard (1); no explicit value that exceeds a documented cap or budget, such as a Codex SessionEnd above 3 or a Claude SessionEnd above 60 (1)
5blocking awareness2For a hook meant to enforce a policy: blocks through exit 2 or a JSON decision supported by the event and host, not exit 1 (1); no interactive command, no reliance on a timeout to block PreToolUse, no unsupported Codex output field (1)
Bonushygiene+1No redundant copy of the same handler within one scope, and $CLAUDE_PROJECT_DIR or a git-root path instead of a hardcoded project-local absolute path

Thresholds:

  • ✅ Good: ≥8/10 (≥80%)
  • ⚠️ Needs work: 5-7/10 (50-79%)
  • ❌ Fix: <5/10 (<50%)

Observational hooks (logging, context injection, formatting, events that cannot block): skip criterion 5 and score on 8 pts max. Flag with 🔵. Decide "meant to enforce a policy" from the script and the user's answer, not from the event name alone.


Execution instructions

Step 1: Discovery

List the locations that exist for each host in scope. Claude Code:

ls ~/.claude/settings.json .claude/settings.json .claude/settings.local.json 2>/dev/null
ls ~/.claude/settings.local.json 2>/dev/null && echo "undocumented location"

Codex:

ls ~/.codex/hooks.json ~/.codex/config.toml .codex/hooks.json .codex/config.toml 2>/dev/null
type codex; which -a codex   # a wrapper first on PATH can inject hooks and bypass trust

Plugins are part of the default scope because their hooks run while the plugin is enabled. For Claude Code, take enabled plugins from enabledPlugins in the effective settings and the installed version of each from ~/.claude/plugins/installed_plugins.json; the plugin cache may hold several versions and temporary checkouts, so read only the installed one. List every plugin hook; score them only when the user asks, since the user does not own that code. For Codex, read plugin hooks/hooks.json or the hooks entry of .codex-plugin/plugin.json.

Also read hooks: blocks in .claude/skills/**/SKILL.md and .claude/agents/**/*.md, following symlinked skill directories. Managed settings and requirements.toml are read-only for this audit: report them, never edit them.

Note which hooks an external installer owns (for example a tool with an install-hooks or upgrade command). Such an installer can restore its entries after a manual cleanup, so recommend a change through that tool, or record the drift risk next to the proposal.

Build a flat list of hook records:

  • host: claude or codex
  • source_file and scope
  • event_type, matcher (or absent)
  • type and its type-specific fields (command, args, url, server/tool, prompt)
  • timeout (seconds, explicit or default), async, asyncRewake, if, once

If no hooks are found in any location, report it and stop.

Done when: every existing location is listed with its host and scope, and each hook has a record (or the audit stopped with "no hooks found").

Step 2: Resolve commands (command hooks only)

Split a shell-form command into shell words before resolving it. A first-word split with awk is wrong: it keeps quotes and splits "$CLAUDE_PROJECT_DIR"/.claude/hooks/x.sh into two words. Use a shell-word parser:

python3 - "$command" "$PROJECT_ROOT" <<'EOF'
import os, shlex, shutil, sys
cmd, root = sys.argv[1], sys.argv[2]
words = shlex.split(cmd.split(';')[-1].split('&&')[-1])
words = [os.path.expanduser(w.replace('${CLAUDE_PROJECT_DIR}', root).replace('$CLAUDE_PROJECT_DIR', root)) for w in words]
while words and (words[0] == 'env' or '=' in words[0].split('/')[0]):
    words.pop(0)  # skip env and VAR=value prefixes
exe = words[0] if words else ''
path = exe if '/' in exe else (shutil.which(exe) or '')
print('executable', exe, 'found' if path and os.path.exists(path) else 'not found', 'x' if path and os.access(path, os.X_OK) else '-')
if os.path.basename(exe) in {'bash', 'sh', 'zsh', 'node', 'python3', 'python', 'deno', 'bun'}:
    script = next((w for w in words[1:] if not w.startswith('-')), '')
    print('script', script, 'found' if os.path.isfile(script) else 'not found', 'readable' if os.access(script, os.R_OK) else '-')
EOF
  • With args (exec form), command is resolved on PATH or as a path; args elements are literal.
  • For bash script.sh, node script.js, python3 script.py, check that the script exists and is readable; it does not need chmod +x.
  • Substitute ${CLAUDE_PROJECT_DIR} with the project root and $(git rev-parse --show-toplevel) with the repository root before testing.
  • For a command chain such as export PATH=...; tool ..., resolve the last command that does the work, and note the chain.
  • A name resolved on PATH now proves only the current shell's PATH, not the environment the host gives hooks. Mark it resolved, and say that the runtime PATH is not verified.

Flag:

  • Not found: nothing exists at that path; for a policy hook this silently disables the gate
  • Not executable: a directly executed script without chmod +x
  • Relative path: resolves from the session cwd, which varies
  • Interactive command (read, fzf, gum, TUI): hooks have no controlling terminal

Done when: every command hook is marked found or not found, executable or not, with the resolved path.

Step 3: Check blocking strategy

For hooks on events that can block (see the tables above) whose command is a local script:

  1. Read the script.
  2. Classify:
    • exit 2 on the block path: ✅
    • JSON decision supported by the event and host (permissionDecision: "deny", decision.behavior: "deny", decision: "block"): ✅, whatever the exit code
    • exit 1 or a generic non-zero code on the block path, without JSON: ⚠️ the action proceeds
    • exit 2 on PermissionRequest (Claude Code): ⚠️ not honored; use the decision object
    • Codex permissionDecision: "ask" or continue: false on PreToolUse: ⚠️ unsupported, the tool call continues
    • No decision at all: 🔵 observational, fine when intended
  3. Flag slow operations (curl, sleep, network calls) without an internal timeout guard in hooks on the interactive path.
  4. Before flagging a slow operation against a short timeout, check whether it runs detached (( ... ) & with disown, nohup, setsid). Detached work outlives the hook, so a short timeout is correct there; report the detached work instead.
  5. For a compiled binary or a script too large to read in full, do not guess its decision logic. Record the version (--version or --help), classify the blocking strategy as UNKNOWN, and propose a behavior canary (a sample payload on stdin, then the exit code and stdout).
  6. Before recommending or dismissing a host-specific variant of a hook (for example a codex subcommand in place of a claude one), run both on the same sample payload from more than one working directory, including a scratch repository outside your usual projects, and compare each output with the host's contract. The output can depend on the directory: with rtk 0.50.0, rtk hook claude returned updatedInput with permissionDecision: "allow" in one repository and without the decision in a scratch repository, which Codex rejects (PreToolUse hook returned updatedInput without permissionDecision:allow); rtk hook codex returned the decision in both. A Claude-named handler under Codex is not a defect only when its output fits the Codex contract in every directory tested.
  7. When static checks pass but behavior is in doubt, run a live canary: a disposable git repository, a fresh session, one small edit prompt. Count the Hook failed lines and, for a checkpointing tool, the records written per edit. Report the host version and the exact failure messages.

Done when: every script-backed hook on a blocking event has one of the classifications above, or UNKNOWN with a proposed canary, and every host-specific variant verdict cites the directories it was tested in.

Step 4: Check duplicates and dead configuration

  • Claude Code: the same handler in several settings files runs once. Report it as redundant configuration to clean up, not as double execution. A plugin or skill copy of the same handler runs separately: report that one as a real duplicate.
  • A matcher on an event without matcher support (silently ignored).
  • if on a non-tool event (the hook never runs).
  • once outside skill frontmatter (ignored).
  • A handler type not allowed on its event (skipped).
  • async: true combined with a decision field (no effect).
  • Two or more PreToolUse hooks on the same matcher returning updatedInput (winner undocumented).
  • A Stop hook that always continues without reading stop_hook_active.
  • Codex: a layer with both hooks.json and inline [hooks]; hooks awaiting trust review. When both representations run the same tool on the same event, even through different command strings (a wrapper script and the binary it calls), report a real double run, and name the installer that owns each entry.
  • Secrets in hook sources or settings: token-like literals, credentials in headers or env. Report the file and line with the value redacted, and recommend moving the value out of the source and rotating it.

Done when: each finding is attached to a hook record, or the list is explicitly empty.

Step 5: Interactive review (core of the skill)

Process hooks one by one. Do not batch and skip the interaction.

For each hook:

Show:

Hook: PreToolUse → Bash [claude, project settings]
type: command
command: ${CLAUDE_PROJECT_DIR}/.claude/hooks/confirm-git-push.sh
timeout: (none, default 600s)
Script: found (executable ✅)
Blocking strategy: JSON permissionDecision "deny" ✅

Ask three questions:

  1. "Does this hook fire at the right time and scope? (y = yes / n = adjust matcher or event)"
  2. "Is the command still working correctly? (y / broken / unsure)"
  3. "Anything to change (matcher, command, timeout, remove it)? (describe or skip)"

For lifecycle hooks (no matcher):

Show:

Hook: SessionEnd [claude, user settings]
type: command
command: ~/.claude/hooks/session-summary.sh
timeout: (none, 1.5s SessionEnd budget)
Script: found (executable ✅)

Ask:

  1. "Does this hook still serve a useful purpose? (y / n)"
  2. "Is the command working within the timeout? (y / broken / unsure)"

If the user provides changes during the interaction: apply them using Edit, confirm each change, then move to the next hook. Never edit managed settings or requirements.toml. After editing a Codex hook, remind the user that it must be trusted again in /hooks.

Done when: every hook has the user's three answers (or two for lifecycle hooks) and every agreed edit is applied and confirmed.

Step 6: Output report

After all hooks are reviewed:

# Hooks Audit: [project or global]
Date: [today] | Scanned: N hooks across M files (Claude Code: X, Codex: Y)

## Summary

| Status | Count |
|--------|-------|
| ✅ Good (≥80%) | N |
| ⚠️ Needs work (50-79%) | N |
| ❌ Fix (<50%) | N |
| 🔵 Observational | N |
| ✅ User confirmed useful | N |
| ⚠️ User flagged for update | N |
| 🗑️ User marked as stale | N |

---

## Per-Hook Results

### PreToolUse → Bash [claude, user settings] (7/10 ⚠️)

type: command
command: `~/.claude/hooks/confirm-git-push.sh`
timeout: (none, default 600s)

| Criterion | Score | Notes |
|-----------|-------|-------|
| event and handler type | ✅ 1/1 | PreToolUse, command |
| matcher | ✅ 2/2 | scoped to Bash |
| command | ✅ 3/3 | found, executable |
| timeout | ⚠️ 1/2 | 600s default on the interactive path, script calls the network without a guard |
| blocking awareness | ⚠️ 0/2 | block path exits 1 without JSON: the tool call proceeds |

**Priority fixes:**
1. Replace `exit 1` with `exit 2`, or print a `permissionDecision: "deny"` JSON object
2. Add `"timeout": 10` or an internal timeout around the network call

User feedback: ✅ scope correct
Content: exit strategy fixed ✅

---

### PostToolUse → Edit|Write [claude, user settings] (8/8 ✅) 🔵

type: command
command: `~/.claude/hooks/anti-ai-markers.sh`
timeout: 2

Observational hook: exit 2 would only show stderr to Claude. All applicable criteria pass. User confirmed still useful.

---

Done when: the report lists every scanned hook with its score and criterion notes.

Step 7: Fix summary

## What Changed This Session

confirm-git-push.sh hook:
  - Exit strategy changed to exit 2
  - Added timeout: 10

session-summary.sh hook:
  - User confirmed useful, no changes

rtk-baseline.sh hook (codex):
  - Edited; needs re-trust in /hooks
  - User flagged as stale (awaiting explicit deletion confirmation)

---
N hooks audited · N edits applied · N flagged as stale · N redundant copies · N awaiting Codex trust review

For any hook the user marked as stale: ask for explicit confirmation before removing it. Never delete without a clear "yes, remove it".

Done when: the summary line counts match the per-hook results and no deletion happened without explicit confirmation.


Edge cases

  • Inline one-liner (e.g. rtk hook claude): skip script-level checks, verify the binary is on PATH
  • bash -c '...' inline: parse the inner script for interactive commands and exit logic
  • exit 1 in a policy hook without JSON: ⚠️ non-blocking, the action proceeds
  • Same hook in user and project settings (Claude Code): runs once; note both locations as redundant configuration
  • timeout: 0: flag as likely invalid
  • Unknown event (e.g. PreToolCall): ❌, report the exact string and suggest the documented name for that host
  • Claude-only event in a Codex file (e.g. PostToolUseFailure, PreModelSwitch) or the reverse (Interrupt in Claude settings): ❌ never fires on that host
  • Script not executable and run directly: fails with a non-blocking error; suggest chmod +x
  • Matcher on a no-matcher event: silently ignored, suggest removing it
  • async: true with decision or permissionDecision: ⚠️ no effect
  • asyncRewake: true: background, wakes Claude on exit 2 with stderr (or stdout); use it instead of async when a background failure must reach Claude
  • prompt or agent hook: no command to resolve; check that prompt is present and that the event allows the type
  • SessionEnd hook doing heavy work: 1.5 s default budget on Claude Code, 1 s default and 3 s maximum on Codex; warn that it may be killed
  • PermissionRequest in -p or background subagents (Claude Code): hooks still run; if none decides, the call is denied. Do not recommend migrating to PreToolUse for that reason
  • if on a non-tool event: the hook never runs; remove if or change the event
  • Stop hook without a stop_hook_active check: continuations stop at the 8-consecutive cap; read the field and exit 0 when it is true
  • Hardcoded project-local absolute path: suggest ${CLAUDE_PROJECT_DIR}/... (Claude Code, exec form preferred) or a git-root-based path (Codex)
  • Hooks not firing as expected: Claude Code: open /hooks (read-only browser showing each hook's source) and run with --debug. Codex: open /hooks to see sources, pending trust reviews, and disabled hooks

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Audit Claude Code agents, skills, and commands for quality and production readiness. Use when evaluating skill quality, checking production readiness scores, or comparing agents against best-practice templates.

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

Codebase health audit scoring 7 categories with progression plan

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

Autonomous improvement loop: scan codebase metrics, scaffold experiment files, run agent-driven iterations until metric improves

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

best-of-n

無料

Generate bounded independent candidates, score them against a frozen rubric, and verify the selected result with a proof log.

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

canary

無料

Post-deploy monitoring: watch production after a deploy and alert on regressions

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

catchup

無料

Restore context after /clear by summarizing recent work and project state

日本語の概要は準備中です。原文の説明を表示しています。

FlorianBruniaux/claude-code-ultimate-guide6,1432026年10月7日 更新

FlorianBruniaux のスキルをすべて見る

このスキルの問題を報告する