Files
everyinc__compound-engineer…/docs/solutions/conventions/resolve-python-interpreter-not-python3.md
2026-08-23 09:55:35 -07:00

5.8 KiB

title, date, category, module, problem_type, component, severity, applies_when, tags
title date category module problem_type component severity applies_when tags
Resolve the Python interpreter by probing execution — never hardcode `python3` in agent-facing prose 2026-07-24 conventions skills (agent-facing reference prose and SKILL.md shell blocks across every skill with bundled scripts) convention tooling high
Writing a SKILL.md or reference doc that instructs an agent to run a bundled script
A bundled Python script works when run by hand but the documented invocation fails
Supporting native Windows contributors (not WSL, not Git Bash with a POSIX Python)
A feature degrades to a fallback path on one platform and never reports why
windows
python
interpreter-resolution
portability
skill-authoring
silent-failure
store-stub

Resolve the Python interpreter by probing execution — never hardcode python3 in agent-facing prose

Context

While adding native Windows support to peer-job-runner.py (issue #1243), the runner itself was made to work — verified end to end on Windows 11. The feature still did not work, because every skill that invokes it tells the agent to run:

python3 "$SKILL_DIR/scripts/peer-job-runner.py" start ...

On native Windows, python3 resolves to the Microsoft Store App Execution Alias stub. It prints an install advertisement and exits non-zero without running Python. The standard python.org Windows installer creates python.exe and py.exe — it never creates python3.exe. So this is the default state of an extremely common setup, not an edge case.

Observed on Windows 11 / Python 3.11:

$ python3 --version
Python was not found; run without arguments to install from the Microsoft Store, ...
$ echo $?
49
$ python --version
Python 3.11.0

The failure surfaced while attempting a sanctioned cross-model review pass: it died at the interpreter, before the runner's own preflight ever ran.

Guidance

Do not hardcode an interpreter name in prose an agent will execute. Resolve it, and resolve it by probing execution, not presence.

PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"; [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; };

Then invoke "$PY" "$SKILL_DIR/scripts/…".

Three rules make this work:

  1. Probe execution, not existence. This is the whole trap. command -v python3 succeeds — the Store stub is a real file on PATH. Every existence check (command -v, which, test -x) passes and the call still fails. Only actually running the interpreter ("$c" -c '') distinguishes them.
  2. Repeat the resolution in every self-contained shell block. Agent harnesses run each tool call in a fresh shell, so a $PY exported in one block does not exist in the next. The repetition is deliberate, not redundancy to factor out — the same reason SKILL_DIR is already set inline in every block in these docs.
  3. Make interpreter failure explicit before the operation crosses its boundary. A required Python operation exits non-zero with a message. An additive workflow may instead log and skip cleanly, but it must do so before provider egress or partial work; silent fallback to a broken interpreter is never acceptable.

Order matters: try python3 first so POSIX hosts keep their canonical name, then python, then py (the Windows launcher, the most reliable there). Note python can also be a Store stub if python.org Python is not installed — which is why the probe, not the order, is what guarantees correctness.

Why This Matters

The historical failure was silent in the worst way: the calling workflow degraded gracefully after dispatch failed, so a Windows user could receive a quietly weaker review. The current code- and doc-review workers resolve one interpreter before provider egress, reuse it for outcome classification and JSON recovery, and log a clean skip if none runs. Their regression fixtures prove the provider is not invoked in that state.

It also invalidates smoke evidence. This project's own Phase C smoke commands invoke python $runner start ..., so the runner passed its verification while the shipped invocation path — the one an agent actually follows — stayed broken. Verify the literal documented invocation, not a hand-adjusted variant.

When to Apply

Any time agent-facing prose or a bundled script names an interpreter or external tool that the host may resolve differently. Python is the instance encountered here; the class is larger. The general form: the name on PATH is not proof the thing runs.

Related repo scope: cross-model review/elevation paths and the remaining bundled-script invocation sites (pr-snapshot, sweep-state.py, validate-*.py, session-history extractors, ce-optimize shell helpers) reuse this exact snippet rather than inventing a variant. Issue #1247 closed after the execution-probe migration; keep the convention because the Store-stub failure mode remains a platform property.

Examples

Before — works on macOS/Linux, silently unreachable on native Windows:

SKILL_DIR="<absolute path…>";
python3 "$SKILL_DIR/scripts/peer-job-runner.py" status "<job-id>" --json

After — self-contained, correct on every host:

SKILL_DIR="<absolute path…>";
PY="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"; [ -n "$PY" ] || { echo "no working Python 3 interpreter on PATH" >&2; exit 1; };
"$PY" "$SKILL_DIR/scripts/peer-job-runner.py" status "<job-id>" --json

The wrong fix, for the record — this passes and still breaks, because the stub satisfies it:

PY=$(command -v python3 || command -v python)   # WRONG: existence, not execution