From 65bade2d2232285d9efc168ac7fe99162b20b447 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 23:03:08 +0000 Subject: [PATCH] Add SessionStart hook installing the pinned dev tools for web sessions Installs pytest and ruff from pyproject.toml's `dev` dependency group, and uv 0.11.29 as pinned in CI, then warms uv's cache for the gate commands in verification.toml. The image preinstalls older uv and ruff earlier on PATH, so the hook puts pip's scripts directory first for the session. It is a no-op outside Claude Code on the web. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TjcFBXJq1YJterj78bETMh --- .claude/hooks/session-start.sh | 36 ++++++++++++++++++++++++++++++++++ .claude/settings.json | 14 +++++++++++++ 2 files changed, 50 insertions(+) create mode 100755 .claude/hooks/session-start.sh create mode 100644 .claude/settings.json diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..f06736f --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Install the pinned dev tools so tests and lint run in Claude Code on the web. +set -euo pipefail + +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "$CLAUDE_PROJECT_DIR" + +# The pins live in pyproject.toml's `dev` dependency group; read them from there +# rather than repeating them, so this hook cannot drift from the gate. +mapfile -t dev_pins < <(python3 -c ' +import tomllib +with open("pyproject.toml", "rb") as f: + print("\n".join(tomllib.load(f)["dependency-groups"]["dev"])) +') + +# Direct use: `python3 -m pytest`, `ruff check .`. +python3 -m pip install --quiet --disable-pip-version-check --root-user-action=ignore "${dev_pins[@]}" + +# The gate commands in verification.toml run through uv, pinned as in CI. +python3 -m pip install --quiet --disable-pip-version-check --root-user-action=ignore uv==0.11.29 + +# The image preinstalls other uv and ruff versions earlier on PATH; put pip's +# scripts directory first so the pinned ones win for the session. +scripts_dir=$(python3 -c 'import sysconfig; print(sysconfig.get_path("scripts"))') +export PATH="$scripts_dir:$PATH" +if [ -n "${CLAUDE_ENV_FILE:-}" ]; then + echo "export PATH=\"$scripts_dir:\$PATH\"" >> "$CLAUDE_ENV_FILE" +fi + +# Warm uv's cache so `uv run --with ` needs no download mid-session. +with_args=() +for pin in "${dev_pins[@]}"; do with_args+=(--with "$pin"); done +uv run --quiet "${with_args[@]}" python -c pass diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..e06b033 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + } +}