Setup
Fires only when you launch Claude Code with --init-only, or with --init or --maintenance in non-interactive mode with the -p flag. It doesn't fire on normal startup. Use it for one-time dependency installation or scheduled cleanup that you trigger explicitly from CI or scripts, separate from normal session startup. For per-session initialization, use SessionStart instead.
The matcher value corresponds to the CLI flag that triggered the hook:
| Matcher | When it fires |
|---|---|
init | claude --init-only or claude -p --init |
maintenance | claude -p --maintenance |
When you run claude --init-only, Claude Code runs Setup hooks and SessionStart hooks with the startup matcher, then exits without starting a conversation.
When you start or continue a conversation with -p, you also need to supply a prompt, as an argument or piped on stdin. You can skip the prompt when a SessionStart hook supplies initialUserMessage or when you resume a session with a deferred tool call.
On success, --init-only prints nothing to the terminal. To confirm the hooks ran, start with claude --debug-file <path> --init-only, replacing <path> with a log file location, and check the log for the Setup and SessionStart hook entries.
Because Setup doesn't fire on every launch, a plugin that needs a dependency installed can't rely on Setup alone. The practical pattern is to check for the dependency on first use and install on miss, for example a hook or skill that tests for $\{CLAUDE_PLUGIN_DATA\}/node_modules and runs npm install if absent. See the persistent data directory for where to store installed dependencies. If you distribute your plugin through a marketplace, you may not need this pattern: Claude Code installs eligible Node.js package dependencies automatically when it caches the plugin.
Setup input
In addition to the common input fields, Setup hooks receive a trigger field set to either "init" or "maintenance":
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
}Setup decision control
Setup hooks can't block; execution continues on any exit code. On every exit code, Claude Code discards a Setup hook's JSON output fields, such as systemMessage, continue, and hookSpecificOutput.additionalContext. With -p, a Setup hook's stdout, stderr, and exit code appear in the run's output only as hook_response events when you launch with --output-format stream-json --verbose.
Setup hooks have access to CLAUDE_ENV_FILE. Variables written to that file persist into subsequent Bash commands for the session, just as in SessionStart hooks. Only type: "command" hooks run on Setup. A type: "mcp_tool" hook on Setup is always skipped, as described under MCP tool hook fields.