Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions plugins/magpie-setup/hooks/check-upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,30 @@ fi

# Once per session, the isolated-setup warning: `setup_preflight` decides
# whether it applies and prints the notice with it, as hook JSON whose
# `systemMessage` the user sees. Without python3 the notice goes out alone.
# `-P` keeps the working directory, which is the project being opened, off
# `sys.path`: `python3 -m` would otherwise put it ahead of `PYTHONPATH`, and
# a `setup_preflight/` package committed to that project would run in place
# of the plugin's own.
# `systemMessage` the user sees. Without a usable Python the notice goes out
# alone.
#
# The hook runs outside the sandbox, so the interpreter is never `python3`
# from PATH, which project settings can set: only fixed system and Homebrew
# locations, which no sandbox grant may cover (the container-gateway hook
# refuses to run at all when one does). uv-managed interpreters are left out:
# trusting them needs the gateway hook's full writable-root checks, and this
# hook only prints a notice, so without a system Python it prints its own.
# It runs with `-I -S`, so no PYTHON* variable, site-packages or working
# directory (the project being opened, which could carry its own
# `setup_preflight/`) reaches `sys.path`; a one-line bootstrap puts the
# plugin's own source there. `setup_preflight` needs only the stdlib.
python=""
for candidate in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
[ -x "$candidate" ] || continue
"$candidate" -I -S -c 'import sys; sys.exit(sys.version_info < (3, 11))' >/dev/null 2>&1 || continue
python="$candidate"
break
done
output="$notice"
if command -v python3 >/dev/null 2>&1; then
output="$(PYTHONPATH="$root/skills/setup" python3 -P -m setup_preflight.isolated session-start \
if [ -n "$python" ]; then
bootstrap='import runpy, sys; sys.path.insert(0, sys.argv.pop(1)); runpy.run_module("setup_preflight.isolated", run_name="__main__", alter_sys=True)'
output="$("$python" -I -S -c "$bootstrap" "$root/skills/setup" session-start \
--project-root "${CLAUDE_PROJECT_DIR:-$PWD}" --notice "$notice" 2>/dev/null)" || output="$notice"
fi

Expand Down
51 changes: 51 additions & 0 deletions tools/setup-preflight/tests/test_session_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,54 @@ def test_a_package_in_the_project_does_not_shadow_the_plugin(project: Path, tmp_
assert not marker.exists()
assert "PLANTED" not in done.stdout
assert "Apache Magpie plugin" in done.stdout


def test_a_python3_on_path_is_never_run(project: Path, tmp_path: Path) -> None:
"""The hook runs outside the sandbox; PATH comes from settings a project can set."""
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
marker = tmp_path / "path-python-ran"
fake = bin_dir / "python3"
fake.write_text(f"#!/bin/sh\ntouch {marker}\nexit 0\n")
fake.chmod(0o755)
env_path = f"{bin_dir}:{os.environ['PATH']}"
done = subprocess.run(
["bash", str(HOOK)],
input="{}",
capture_output=True,
text=True,
env={
**os.environ,
"PATH": env_path,
"CLAUDE_PLUGIN_ROOT": str(PLUGIN_ROOT),
"CLAUDE_PLUGIN_DATA": str(tmp_path / "data"),
"CLAUDE_PROJECT_DIR": str(project),
},
cwd=project,
check=False,
)
assert done.returncode == 0, done.stderr
assert not marker.exists()
assert "Apache Magpie plugin" in done.stdout


def test_pythonpath_does_not_reach_the_preflight(project: Path, tmp_path: Path) -> None:
"""-I drops PYTHONPATH: a planted setup_preflight there never runs."""
planted = tmp_path / "pp" / "setup_preflight"
planted.mkdir(parents=True)
marker = tmp_path / "pp-ran"
code = f"open({str(marker)!r}, 'w').close()\n"
(planted / "__init__.py").write_text(code, encoding="utf-8")
(planted / "isolated.py").write_text(code, encoding="utf-8")
env = {
**os.environ,
"PYTHONPATH": str(tmp_path / "pp"),
"CLAUDE_PLUGIN_ROOT": str(PLUGIN_ROOT),
"CLAUDE_PLUGIN_DATA": str(tmp_path / "data"),
"CLAUDE_PROJECT_DIR": str(project),
}
done = subprocess.run(
["bash", str(HOOK)], input="{}", capture_output=True, text=True, env=env, cwd=project, check=False
)
assert done.returncode == 0, done.stderr
assert not marker.exists()
Loading