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 |
|
|
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:
- Probe execution, not existence. This is the whole trap.
command -v python3succeeds — the Store stub is a real file onPATH. Every existence check (command -v,which,test -x) passes and the call still fails. Only actually running the interpreter ("$c" -c '') distinguishes them. - Repeat the resolution in every self-contained shell block. Agent harnesses run each
tool call in a fresh shell, so a
$PYexported in one block does not exist in the next. The repetition is deliberate, not redundancy to factor out — the same reasonSKILL_DIRis already set inline in every block in these docs. - 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