diff --git a/leetcode_coach/leetcode_coach/leetcode b/leetcode_coach/leetcode_coach/leetcode index 7b0b85b..ce07d85 100755 --- a/leetcode_coach/leetcode_coach/leetcode +++ b/leetcode_coach/leetcode_coach/leetcode @@ -6,13 +6,12 @@ # from a client that waits for hooks, and the interactive TUI streams first. # leetcode [what you are stuck on...] # -# Symlinked from ~/.local/bin/leetcode. The package lives in leetcode-guard; -# the repo root goes on PYTHONPATH so the launcher works from any checkout, -# installed or not, and -P keeps the caller's cwd off sys.path. +# Symlinked from ~/.local/bin/leetcode. The package is found through +# install.sh's editable install, like the Stop-hook shim; no PYTHONPATH here, +# because the REPL passes its environment on to `claude -p` and every hook and +# Bash call of the coached session would inherit it. -P keeps cwd off sys.path. # ============================================================================ set -euo pipefail -repo="$(dirname "$(dirname "$(readlink -f "$0")")")" -PYTHONPATH="${repo}${PYTHONPATH:+:${PYTHONPATH}}" \ - exec /usr/bin/python3 -P -m leetcode_coach.leetcode_repl "$@" +exec /usr/bin/python3 -P -m leetcode_coach.leetcode_repl "$@" diff --git a/leetcode_coach/leetcode_coach/tests/test_leetcode_repl.py b/leetcode_coach/leetcode_coach/tests/test_leetcode_repl.py index 3734974..3e7bd14 100644 --- a/leetcode_coach/leetcode_coach/tests/test_leetcode_repl.py +++ b/leetcode_coach/leetcode_coach/tests/test_leetcode_repl.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import os from pathlib import Path import subprocess from typing import Any @@ -167,8 +168,17 @@ def test_help_prints_usage(capsys: pytest.CaptureFixture[str]) -> None: def test_launcher_runs_the_package_repl() -> None: + # The real launcher finds the package through install.sh's editable + # install; PYTHONPATH stands in for it here (-P still honours it). + env = {**os.environ, "PYTHONPATH": str(LAUNCHER.resolve().parents[1])} proc = subprocess.run( - [str(LAUNCHER), "--help"], capture_output=True, text=True, check=False + [str(LAUNCHER), "--help"], capture_output=True, text=True, check=False, env=env ) assert proc.returncode == 0, proc.stderr assert proc.stdout.startswith("Usage: leetcode [--local]") + + +def test_launcher_does_not_leak_pythonpath_into_the_session() -> None: + """The REPL hands its environment to `claude -p`; the launcher adds nothing.""" + body = LAUNCHER.read_text(encoding="utf-8").split("set -euo pipefail", 1)[1] + assert "PYTHONPATH" not in body