Found by CodeRabbit on #576 and confirmed. Fixed in the release skill there; this issue covers the rest of the repo.
The defect
The documented cross-host interpreter convention is:
python3 "<script>" <args> || python "<script>" <args>
|| branches on the exit code, so it cannot distinguish "there is no python3" from "the helper ran and told you something". Any non-zero exit re-runs the whole command under python and reports the second run's code.
That is fine for a helper whose only meaningful answer is 0/non-zero. It is actively wrong for one whose exit codes carry information — and this repo now has several:
| Helper |
Codes |
_releaselib.py run-pre-tag |
5 = a check reported drift, 6 = a check mutated the tree |
_releaselib.py semver-greater |
1 = not greater, 2 = could not compare |
_releaselib.py check-manifests |
1 = a path disagrees, 2 = a manifest could not be parsed |
releasehash.py check |
1 = commands changed, 2 = never confirmed |
taskwrite.py archive |
3 = the append-only archive could not be read |
For run-pre-tag the consequence is concrete: a drift result re-executes the project's declared pre-tag commands a second time and discards the first verdict.
The fix
Resolve the interpreter once, by presence, then use it:
PY=python3; command -v python3 >/dev/null 2>&1 || PY=python
"$PY" "<script>" <args>
command -v tests whether the interpreter EXISTS, which is the actual condition the fallback is for.
Scope
Already fixed in core/surface/skills/release/SKILL.md (#576). Remaining: every other skill and command surface using the || spelling. Worth a sweep with a guard afterwards, so the pattern cannot come back.
grep -rn '|| python' core/surface/
Note the same || shape in hooks.json is fine and should be left alone: those hooks are pass/fail, with no exit-code vocabulary to lose.
Refs #563, #576.
Found by CodeRabbit on #576 and confirmed. Fixed in the
releaseskill there; this issue covers the rest of the repo.The defect
The documented cross-host interpreter convention is:
||branches on the exit code, so it cannot distinguish "there is nopython3" from "the helper ran and told you something". Any non-zero exit re-runs the whole command underpythonand reports the second run's code.That is fine for a helper whose only meaningful answer is 0/non-zero. It is actively wrong for one whose exit codes carry information — and this repo now has several:
_releaselib.py run-pre-tag_releaselib.py semver-greater_releaselib.py check-manifestsreleasehash.py checktaskwrite.py archiveFor
run-pre-tagthe consequence is concrete: a drift result re-executes the project's declared pre-tag commands a second time and discards the first verdict.The fix
Resolve the interpreter once, by presence, then use it:
command -vtests whether the interpreter EXISTS, which is the actual condition the fallback is for.Scope
Already fixed in
core/surface/skills/release/SKILL.md(#576). Remaining: every other skill and command surface using the||spelling. Worth a sweep with a guard afterwards, so the pattern cannot come back.Note the same
||shape inhooks.jsonis fine and should be left alone: those hooks are pass/fail, with no exit-code vocabulary to lose.Refs #563, #576.