SpecMatter field notes

Claude Code / hook lifecycle field notes

Claude Code Hooks: events, matchers, and a Windows test

Short answer: a Claude Code hook is an event-driven handler configured under hooks. Choose the lifecycle event, narrow it with a matcher when the event supports one, and attach a command, HTTP, MCP, prompt, or agent handler. On Windows, make the shell explicit or use exec form with a real executable. In our local Claude Code 2.1.283 test, a command hook captured Setup:init before authentication and before any model request.

That result proves one narrow thing: a local Setup command hook can run in this environment without a paid Claude request. It does not prove that tool-call hooks, prompt hooks, or agent hooks work without an authenticated session.

A terminal event becomes JSON, passes a matcher, runs a local handler, and writes an audit log while a model branch stays blocked
Event, JSON input, matcher, handler, evidence. The blocked branch marks behavior that was not tested without a Claude session.

Local test: Windows 11, Claude Code 2.1.283, 2026-09-27. No Claude login, no API key, no model request, and no charge.

The hook shape in one table

LayerQuestion it answersExample
EventWhen should Claude Code consider the hook?Setup, PreToolUse, PostToolUse, Stop
MatcherWhich occurrences should activate the group?init for Setup; Bash for a tool event
HandlerWhat executes?A command, HTTP endpoint, MCP tool, prompt, or agent
OutputDoes the handler add context, allow, deny, or report nothing?Exit code plus optional JSON on stdout

The official reference says command-hook input arrives as JSON on stdin. An exit code of zero with no decision means the normal flow continues; it is not an automatic approval. Tool hooks can deny a call, but the permission system remains the harder enforcement boundary.

A Windows-safe Setup hook

This test used exec form: command was the real node.exe executable and args carried the script and output paths. That avoids shell quoting and the Windows limitation that .cmd shims are not native executables.

{
  "hooks": {
    "Setup": [{
      "matcher": "init",
      "hooks": [{
        "type": "command",
        "command": "node",
        "args": [
          "${CLAUDE_PROJECT_DIR}/.claude/hooks/capture-event.mjs",
          "${CLAUDE_PROJECT_DIR}/hook-events.jsonl"
        ]
      }]
    }]
  }
}
claude --init-only \
  --settings .claude/settings.json \
  --setting-sources project \
  --debug-file setup-debug.log

The first attempt reached Setup:init but failed before the handler because Claude Code could not create its user-level .claude\session-env directory. After granting the CLI normal write access to its own configuration directory, the second attempt exited zero and wrote this event:

{"session_id":"ff20f6a5-f8bb-4b55-9c96-dd8a2fc02abd","cwd":"C:\\Users\\Administrator\\Documents\\Codex\\2026-09-24\\javascript-ilang-type-command-course-ai\\work\\claude-code-lab-2026-09-27","hook_event_name":"Setup","trigger":"init"}

Download the local hook observations (CSV)

What was tested, and what remains untested

ClaimVerdictEvidence
Claude Code was installed and runnableTested2.1.283 (Claude Code); doctor ended with No installation issues found.
The Setup command hook ran without a model callTestedExit code 0 and a JSONL record with hook_event_name: Setup and trigger: init.
PreToolUse or PostToolUse firedUntestedNo authenticated model session made a tool call.
A prompt or agent hook returned a decisionUntestedThose handlers require a model-backed session; none was run.
The same fixture behaves identically on another releaseUntestedOnly version 2.1.283 was installed.
Evidence rule: a hook configuration that looks valid is not the same as an event that fired. Keep the exact CLI version, command, exit code, debug log, and handler output together.

Where hooks live and what that changes

LocationScopeOperational note
~/.claude/settings.jsonAll local projectsMachine-local; easy to forget when reproducing elsewhere.
.claude/settings.jsonOne projectShareable and reviewable with the repository.
.claude/settings.local.jsonOne projectLocal override; normally not committed.
Plugin, skill, or subagent metadataComponent lifetimeRegistration and trust rules differ by component.

On Windows, shell form can run through Git Bash or PowerShell depending on the machine. Use shell: "powershell" when you need PowerShell behavior, or prefer exec form for a script path and pass every argument separately.

FAQ

Can command hooks run before Claude is logged in?

The local Setup fixture did on Claude Code 2.1.283. This result is limited to the Setup command hook tested here; it does not establish behavior for every event or handler type.

Does a silent exit code 0 approve a tool call?

No. The official reference describes it as no hook decision; the normal permission flow still applies.

Did this test spend money?

No. No model request was sent. The installed CLI reported no login and no API key.

Sources and related field notes

Official documentation checked 2026-09-27. Local results come from the isolated fixture described above. SpecMatter is not affiliated with Anthropic.