/hooks:session-start-hook
Generate a SessionStart hook that prepares your repository for Claude Code on the web — installing dependencies, configuring environment variables, and verifying that tests and linters work.
When to Use This Skill
Use this skill when... Use /hooks:hooks-configuration instead when...
Setting up a repo for Claude Code on the web Configuring other hook types (PreToolUse, Stop, etc.)
Need automatic dependency install in web sessions Need general hooks knowledge or debugging
Want tests/linters verified on session start Writing custom hook logic from scratch
Onboarding a project to remote Claude Code Understanding hook lifecycle events
Context
Detect project stack:
-
Lockfiles: !find . -maxdepth 1 ( -name 'package-lock.json' -o -name 'yarn.lock' -o -name 'pnpm-lock.yaml' -o -name 'bun.lockb' -o -name 'poetry.lock' -o -name 'uv.lock' -o -name 'Cargo.lock' -o -name 'go.sum' -o -name 'Gemfile.lock' )
-
Project files: !find . -maxdepth 1 ( -name 'package.json' -o -name 'pyproject.toml' -o -name 'requirements.txt' -o -name 'Cargo.toml' -o -name 'go.mod' -o -name 'Gemfile' -o -name 'pom.xml' ) -o -maxdepth 1 -name 'build.gradle*'
-
Linter configs: !find . -maxdepth 1 ( -name 'biome.json' -o -name 'biome.jsonc' -o -name '.eslintrc*' -o -name 'eslint.config.*' )
-
Existing settings: !find .claude -maxdepth 1 -name 'settings.json' -type f
-
Existing hooks dir: !find . -maxdepth 2 -type d -name 'scripts'
Parameters
Flag Default Description
--remote-only
off Wrap script in CLAUDE_CODE_REMOTE guard — hook exits immediately in local sessions
--no-verify
off Skip test/linter verification step in the generated script
Execution
Step 1: Detect project stack
Identify all languages and tooling from the context above.
Language detection:
File Present Language Package Manager (from lockfile)
package.json
Node.js npm (package-lock.json ), yarn (yarn.lock ), pnpm (pnpm-lock.yaml ), bun (bun.lockb )
pyproject.toml
Python poetry (poetry.lock ), uv (uv.lock ), pip (fallback)
requirements.txt
Python pip
Cargo.toml
Rust cargo
go.mod
Go go modules
Gemfile
Ruby bundler
pom.xml
Java maven
build.gradle*
Java/Kotlin gradle
Test runner detection:
Language How to Detect Test Command
Node.js scripts.test in package.json npm test / bun test / etc.
Python [tool.pytest] in pyproject.toml, or pytest in deps pytest
Rust always available cargo test
Go always available go test ./...
Ruby Gemfile contains rspec or minitest
bundle exec rspec / bundle exec rake test
Java pom.xml / build.gradle mvn test / gradle test
Linter detection:
Config File Linter Command
biome.json / biome.jsonc
Biome npx biome check .
.eslintrc* / eslint.config.*
ESLint npx eslint .
[tool.ruff] in pyproject.toml Ruff ruff check .
Cargo.toml
Clippy cargo clippy
Report detected stack to user before generating.
Step 2: Generate hook script
Create the script at scripts/claude-session-start.sh (or .claude/hooks/session-start.sh if no scripts/ directory exists).
Script template — adapt per detected stack:
#!/bin/bash
Claude Code SessionStart Hook
Generated by /hooks:session-start-hook
Installs dependencies and verifies tooling for web sessions
{{ if --remote-only }}
Only run in remote/web sessions
if [ "$CLAUDE_CODE_REMOTE" != "true" ]; then exit 0 fi {{ endif }}
CONTEXT_PARTS=()
──── Dependency Installation ────
{{ if Node.js detected }} if [ -f "package.json" ]; then {{ npm ci / yarn install --frozen-lockfile / pnpm install --frozen-lockfile / bun install --frozen-lockfile }} CONTEXT_PARTS+=("Node.js dependencies installed via {{ pm }}") fi {{ endif }}
{{ if Python detected }} if [ -f "pyproject.toml" ]; then {{ poetry install / uv sync / pip install -e '.[dev]' }} CONTEXT_PARTS+=("Python dependencies installed via {{ pm }}") elif [ -f "requirements.txt" ]; then pip install -r requirements.txt 2>/dev/null CONTEXT_PARTS+=("Python dependencies installed via pip") fi {{ endif }}
{{ if Rust detected }} if [ -f "Cargo.toml" ]; then cargo fetch 2>/dev/null CONTEXT_PARTS+=("Rust dependencies fetched") fi {{ endif }}
{{ if Go detected }} if [ -f "go.mod" ]; then go mod download 2>/dev/null CONTEXT_PARTS+=("Go dependencies downloaded") fi {{ endif }}
{{ if Ruby detected }} if [ -f "Gemfile" ]; then bundle install 2>/dev/null CONTEXT_PARTS+=("Ruby dependencies installed via bundler") fi {{ endif }}
{{ if Java/maven detected }} if [ -f "pom.xml" ]; then mvn dependency:resolve -q 2>/dev/null CONTEXT_PARTS+=("Java dependencies resolved via maven") fi {{ endif }}
{{ if Java/gradle detected }} if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then gradle dependencies --quiet 2>/dev/null CONTEXT_PARTS+=("Java/Kotlin dependencies resolved via gradle") fi {{ endif }}
──── Environment Variables ────
if [ -n "$CLAUDE_ENV_FILE" ]; then {{ per language: export PATH, NODE_ENV, PYTHONDONTWRITEBYTECODE, etc. }} fi
{{ unless --no-verify }}
──── Verify Tooling ────
{{ if test runner detected }} if {{ test command --bail / -x / quick mode }} 2>/dev/null; then CONTEXT_PARTS+=("Tests: passing") else CONTEXT_PARTS+=("Tests: FAILING - investigate before making changes") fi {{ endif }}
{{ if linter detected }} if {{ lint command --max-diagnostics=0 / --quiet }} 2>/dev/null; then CONTEXT_PARTS+=("Linter: clean") else CONTEXT_PARTS+=("Linter: issues detected") fi {{ endif }} {{ end unless }}
──── Report Context ────
CONTEXT=$(printf '%s\n' "${CONTEXT_PARTS[@]}") if [ -n "$CONTEXT" ]; then jq -n --arg ctx "$CONTEXT" '{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": $ctx } }' fi
exit 0
Adapt the template by:
-
Including only sections for detected languages
-
Using the correct package manager commands with frozen lockfile flags
-
Using the correct test runner and linter commands
-
Setting appropriate environment variables per language
Step 3: Configure .claude/settings.json
Read existing .claude/settings.json if it exists. Merge the SessionStart hook — preserve all existing configuration.
If a SessionStart hook already exists, ask the user whether to:
-
Replace the existing SessionStart hook
-
Add alongside the existing hook (both will run)
-
Abort and keep existing configuration
Configuration to merge:
{ "hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [ { "type": "command", "command": "bash "$CLAUDE_PROJECT_DIR/scripts/claude-session-start.sh"", "timeout": 120 } ] } ] } }
Use timeout: 120 (2 minutes) for dependency installation. Adjust path if script is in .claude/hooks/ instead of scripts/ .
Step 4: Finalize
-
Make the script executable: chmod +x <script-path>
-
Create .claude/ directory if needed for settings.json
-
Report summary of what was created
Step 5: Verify (unless --no-verify)
Run the generated script locally to confirm it executes without errors. Report results.
Post-Actions
After generating the hook:
-
Suggest committing the new files: scripts/claude-session-start.sh .claude/settings.json
-
If --remote-only was NOT used, mention the flag for web-only behavior
-
If the project needs network access beyond defaults, remind about Claude Code web network settings
-
Mention matcher options: "startup" (new sessions), "resume" (resumed), "" (all events)
SessionStart Matcher Reference
Matcher Fires When
"startup"
New session starts
"resume"
Session is resumed
"clear"
After /clear command
"compact"
After context compaction
"" (empty) All SessionStart events
Agentic Optimizations
Context Approach
Quick setup, skip verification /hooks:session-start-hook --remote-only --no-verify
Full setup with verification /hooks:session-start-hook
Web-only with tests /hooks:session-start-hook --remote-only
Dependency install commands Use --frozen-lockfile / ci variants for reproducibility
Test verification Use --bail=1 / -x for fast failure
Linter verification Use --max-diagnostics=0 / --quiet for pass/fail only
Quick Reference
Item Value
Script location scripts/claude-session-start.sh or .claude/hooks/session-start.sh
Settings location .claude/settings.json
Timeout 120 seconds (adjustable)
Output format JSON with hookSpecificOutput.additionalContext
Environment persistence Via CLAUDE_ENV_FILE
Remote detection CLAUDE_CODE_REMOTE=true