Skip to content

modern-python PATH shim silently fails bare python3 in Claude Code hook contexts #162

@adelaidasofia

Description

@adelaidasofia

Problem

The modern-python/hooks/setup-shims.sh SessionStart hook prepends a python3 shim to PATH that always exit 1s. This breaks every Claude Code settings.json hook that invokes bare python3 /path/to/hook.py: the hook command silently fails because most are wrapped with 2>/dev/null || true (or rely on the harness treating exit 1 as a soft skip), so the user never sees the shim's stderr nudge.

Concrete impact on my setup (~25 user-defined hooks broken since 1.5.0 landed on my system on 2026-05-09):

  • Stop hooks (worktree-archive-autoprep, check-fabricated-hook-attribution, check-fabricated-panelist)
  • SessionEnd hooks (reconcile-worktree-shared)
  • PreToolUse hooks (vault-command-nudges, block-raw-vault-git, several others)
  • PostToolUse hooks (hookify-auto-commit, etc.)

The shim's intent is a teaching nudge for human-typed python3 -m pip install. In automated contexts, no human reads the nudge. The cost is silent breakage.

Repro

  1. Install trailofbits/modern-python plugin.
  2. Add any user hook to ~/.claude/settings.json that runs python3 /path/to/script.py.
  3. Trigger the hook. Script never executes; stderr goes to harness log only.

Suggested fix

Bypass the shim when CLAUDECODE=1 (Claude Code sets this in every subprocess: hooks, Bash tool, agents). The nudge still fires in plain terminal shells where the user typed python3 ... directly without invoking Claude Code.

Verified patch against 1.5.0 hooks/shims/python (top of file, before the case statement):

if [[ "${CLAUDECODE:-}" == "1" ]]; then
  shim_dir="${BASH_SOURCE[0]%/*}"
  shim_dir="$(cd "$shim_dir" && pwd)"
  clean_path=":$PATH:"
  clean_path="${clean_path//:$shim_dir:/:}"
  clean_path="${clean_path#:}"
  clean_path="${clean_path%:}"
  PATH="$clean_path" exec "$cmd" "$@"
fi

If preferred, narrower variants:

  • Skip only on python3 /absolute/path/to/script.py invocations (let -m, -c keep nudging).
  • Skip when stdin is not a tty.

CLAUDECODE=1 is the cleanest because it precisely targets the harm domain (Claude Code automation) without affecting the nudge's intended audience (humans typing in terminal).

Happy to send a PR.

Environment

  • macOS, Claude Code 2.1.128
  • modern-python plugin 1.5.0 (installed 2026-05-09 23:27 local)
  • Affected hook surfaces: PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd, UserPromptSubmit

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions