home / skills / laurigates / claude-plugins / hooks-session-start-hook

hooks-session-start-hook skill

/hooks-plugin/skills/hooks-session-start-hook

This skill helps generate a SessionStart hook that installs dependencies, runs tests and linters on web sessions for Claude Code.

npx playbooks add skill laurigates/claude-plugins --skill hooks-session-start-hook

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
9.7 KB
---
model: haiku
name: hooks-session-start-hook
description: |
  Create a SessionStart hook for Claude Code on the web. Use when setting up a repository
  for remote Claude Code sessions, ensuring dependencies install and tests/linters run
  automatically on session start. Detects project type, package manager, test runner, and linter.
args: "[--remote-only] [--no-verify]"
allowed-tools: Read, Write, Edit, Glob, Grep, Bash(test *), Bash(cat *), Bash(chmod *), Bash(mkdir *), Bash(jq *), TodoWrite
argument-hint: "--remote-only to only run in web sessions, --no-verify to skip test verification"
created: 2026-02-07
modified: 2026-02-10
reviewed: 2026-02-07
---

# /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' \) 2>/dev/null`
- 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*' 2>/dev/null`
- Linter configs: !`find . -maxdepth 1 \( -name 'biome.json' -o -name 'biome.jsonc' -o -name '.eslintrc*' -o -name 'eslint.config.*' \) 2>/dev/null`
- Existing settings: !`cat .claude/settings.json 2>/dev/null`
- Existing hooks dir: !`find . -maxdepth 2 -type d -name 'scripts' 2>/dev/null`

## 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:**

```bash
#!/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:
1. Including only sections for detected languages
2. Using the correct package manager commands with frozen lockfile flags
3. Using the correct test runner and linter commands
4. 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:

```json
{
  "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

1. Make the script executable: `chmod +x <script-path>`
2. Create `.claude/` directory if needed for settings.json
3. 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:

1. Suggest committing the new files:
   ```
   scripts/claude-session-start.sh
   .claude/settings.json
   ```
2. If `--remote-only` was NOT used, mention the flag for web-only behavior
3. If the project needs network access beyond defaults, remind about Claude Code web network settings
4. 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` |

Overview

This skill generates a SessionStart hook script that prepares a repository for Claude Code on the web by installing dependencies, exporting environment variables, and optionally running tests and linters. It detects project type, package manager, test runner, and linter, then creates an executable script and merges a SessionStart entry into .claude/settings.json. The generated hook reports a concise JSON summary of what it did.

How this skill works

The skill scans common lockfiles and project files to detect languages and tooling (Node.js, Python, Rust, Go, Ruby, Java, etc.). It composes a bash script that installs dependencies using the appropriate package manager (with frozen-lockfile/ci options where available), loads env vars if CLAUDE_ENV_FILE is set, and runs tests and linters unless --no-verify is passed. Finally it writes or merges the SessionStart configuration into .claude/settings.json and makes the script executable.

When to use it

  • Onboarding a repository for remote Claude Code web sessions
  • You want automatic dependency installation on session start
  • You want tests and linters verified when a session begins
  • When you need a reproducible, locked install (ci/frozen-lockfile) in web sessions
  • When you want the hook gated to remote sessions only (--remote-only)

Best practices

  • Use --remote-only for web-only execution to avoid local runs
  • Prefer package-manager ci/frozen-lockfile commands for reproducibility
  • Pass --no-verify to skip slow test/lint verification for quick setup
  • Keep timeout at 120s for installs; increase if your project needs more time
  • Review the generated script before committing and run it locally to confirm behavior

Example use cases

  • A Node.js repo with package-lock.json: installs via npm ci, runs npm test and eslint on start
  • A Python repo with pyproject.toml: runs poetry/uv install and pytest/ruff checks on session start
  • A Go or Rust project: downloads modules or fetches crates, runs go test ./... or cargo test
  • Multi-language monorepo: installs per-language deps and reports combined context JSON
  • Onboarding contributors: ensure linters/tests run automatically in remote sessions

FAQ

Where is the script placed?

The script is created at scripts/claude-session-start.sh; if scripts/ doesn't exist it is placed at .claude/hooks/session-start.sh, and made executable.

What happens if a SessionStart hook already exists?

You are prompted to replace it, add the new hook alongside it, or abort. The skill merges settings.json while preserving other entries.