Skip to content

fix(hooks): resolve the interpreter once by presence, never a python3-X-or-python-X fold (#577) - #614

Merged
SUaDtL merged 1 commit into
mainfrom
fix/577-interpreter-fold
Aug 5, 2026
Merged

fix(hooks): resolve the interpreter once by presence, never a python3-X-or-python-X fold (#577)#614
SUaDtL merged 1 commit into
mainfrom
fix/577-interpreter-fold

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

The python3 X || python X cross-host interpreter convention branches on the
helper's EXIT CODE, so it cannot distinguish "no python3" from "the helper
ran and told you something." Any helper whose exit code carries information
(not just 0/non-zero) gets silently re-run under python on any nonzero
exit, and the caller sees only the second run's verdict — already fixed in
release/SKILL.md for #576; this closes the rest of the repo per #577.

Every remaining site now resolves the interpreter ONCE, by presence, before
invoking a helper:

PY=python3; { command -v python3 >/dev/null 2>&1 && python3 --version >/dev/null 2>&1; } || PY=python
"$PY" "<script>" <args>

Call-site inventory

Fixed (core/surface, canonical — regenerated into all three plugin trees via tools/build-surface.py):

  • commands/doctor.mddoctor.py
  • commands/metrics.mdmetrics.py (x2 call sites + prose)
  • commands/override.mdsecurity-pass.py (Security ceiling heavier path)
  • commands/pr.mdbabysit.py
  • commands/preview.mdpreview.py (diff and secrets modes)
  • commands/prune.mdprune-transcript.py (x2)
  • commands/standup.mdboardsync.py reconcile, taskwrite.py archive (the two named in The python3 X || python X interpreter convention masks helper exit codes repo-wide #577's own table)
  • commands/task.mdtaskwrite.py (also normalized start/done, which had no fallback at all before, to the same "$PY" spelling for internal consistency)
  • commands/watch.mdbabysit.py
  • includes/security-gate-record.mdsecurity-pass.py (shared by crypto-compliance/secret-handling, which reference it rather than duplicating the command)
  • skills/commit-gate/SKILL.mdmigration-pass.py
  • skills/tribunal/SKILL.mdtribunal-usage.py observe (Claude-only leg, ${CLAUDE_PLUGIN_ROOT})

Also updated the matching "Invoked by ... as" header-comment examples in
core/pysrc/{babysit,metrics,migration-pass,preview,security-pass, taskwrite}.py so the documented invocation contract stays consistent with
the command prose that actually calls them.

Deliberately keptplugins/*/hooks/hooks.json's PreToolUse/
SessionStart dual registration (python -c "..." || python3 "<script>").
Per #577's own carve-out: these hooks are pass/fail with no exit-code
vocabulary to lose, so the fold's information-loss defect doesn't apply.
docs/hooks.md correctly describes this hooks.json behavior and needed no
change. _hooklib.py/_githooks.py already carry comments explaining why
the git-level shim and hooks.json avoid the fold — explanatory, not
instances of it.

Reported, not fixedplugins/ca-pi/tools/src/bridge.ts
(resolvePythonCommand). This already resolves-by-presence correctly: it
probes each interpreter candidate directly via a dedicated spawnSync(..., ["-c", "import sys; ..."]) call, entirely separate from invoking the actual
helper, so there's no shared exit code to conflate. Not an instance of the
#577 defect; no change needed.

Out of scope (bare python3 X with no fallback at all, a different
defect than the || fold this issue targets): skills/context-creation/ SKILL.md and skills/debug/SKILL.md's taskwrite.py add calls.

Version advance

ca 2.11.7 → 2.11.8, ca-codex 0.4.6 → 0.4.7, ca-pi 0.2.6 → 0.2.7, root
package.json regenerated, README badge + ships-line updated, CHANGELOG.md
and plugins/ca-pi/CHANGELOG.md sections added.

Test results

  • python tools/build-surface.py --check → OK (claude, codex, pi in sync)
  • python tools/sync-core.py --check → OK (56 core files x 3 plugins, byte-identical)
  • git diff --check → clean
  • env -u NO_COLOR python -m pytest plugins/ca/hooks/tests -q → 1301 passed,
    130 subtests passed, 10 failed — all 10 are the pre-existing Worktree sessions: security-pass.py and H-09b/H-10b resolve different project roots — gate pass unrecordable #604
    worktree-root family (test_git_hooks.py/test_repo_resolution.py,
    "leaving the shared enforcer entry as it is... Start a session from the
    main checkout to refresh it"), reproduced identically on a stashed clean
    origin/main in the same worktree; this diff touches no git-hook file.
  • python -m pytest .github/scripts/test_build_surface.py -q → 40 passed
  • python .github/scripts/test_routing_and_cleanup_surface.py → 19 passed (OK)
  • python .github/scripts/check_badge_consistency.py → consistent
  • python .github/scripts/test_release_trace.py → 29 passed
  • python .github/scripts/payload_version_gate.py --plugin plugins/ca --base origin/main → 2.11.7 -> 2.11.8
  • python .github/scripts/payload_version_gate.py --plugin plugins/ca-codex --base origin/main → 0.4.6 -> 0.4.7
  • python tools/build-host-packages.py --check --release-guard-base origin/main → Pi payload/version/changelog/root metadata advanced together: 0.2.6 -> 0.2.7
  • python .github/scripts/check_site_voice.py → 39 authored pages clean

No new tests were added — this is a prose/documentation and doc-comment fix
with no new executable code path, so the mutation-proof requirement (every
NEW test dies to a mutant) doesn't apply; the existing suites above cover
the unchanged executable surfaces.

Parity watch

No unexplained ca/ca-codex/ca-pi divergence — the fix is generated
identically into all three trees from the same core/surface source, and the
Claude-only tribunal-usage.py leg is the one place ${CLAUDE_PLUGIN_ROOT}
(shell-resolved) is used instead of {{PLUGIN_ROOT}} (build-time token),
which is pre-existing and unrelated to this change.

Closes #577

https://claude.ai/code/session_01QjJeSbcwPHwMmd6CEZeagB

…-X-or-python-X fold (#577)

Every command/skill surface that spelled the cross-host interpreter
fallback as `python3 X || python X` branched on the helper's exit
code, so any helper whose exit code carries information (e.g.
`taskwrite.py archive`'s "archive could not be read", exit 3) got
silently re-run under `python` on ANY nonzero exit, and the caller
saw only the second run's verdict. Already fixed in the `release`
skill (#576); this closes the rest of the repo.

Fixed the fold in core/surface/commands/{doctor,metrics,override,pr,
preview,prune,standup,task,watch}.md, core/surface/includes/
security-gate-record.md, and core/surface/skills/{commit-gate,
tribunal}/SKILL.md, plus the matching "Invoked by ... as" header
comments in core/pysrc/{babysit,metrics,migration-pass,preview,
security-pass,taskwrite}.py. Every site now resolves once via
`PY=python3; { command -v python3 >/dev/null 2>&1 && python3
--version >/dev/null 2>&1; } || PY=python` and invokes `"$PY"`.
plugins/*/hooks/hooks.json is unchanged by design (#577's own
carve-out): its PreToolUse/SessionStart entries are pass/fail with
no exit-code vocabulary to lose.

Regenerated all three plugin trees via `tools/build-surface.py` and
`tools/sync-core.py` (both --check clean). Version advance: ca
2.11.8, ca-codex 0.4.7, ca-pi 0.2.7, root package.json regenerated,
README badge/ships-line updated, CHANGELOG + ca-pi CHANGELOG sections
added.

CHANGELOG: Command/skill surfaces resolve the Python interpreter once by presence instead of a python3-or-python exit-code fold that could discard a helper's real verdict.
Claude-Session: https://claude.ai/code/session_01QjJeSbcwPHwMmd6CEZeagB
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: bf3c4071-e971-4f3f-a186-6ed8a1e2751f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@SUaDtL
SUaDtL merged commit e24f9ec into main Aug 5, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The python3 X || python X interpreter convention masks helper exit codes repo-wide

1 participant