Skip to content

fix(hooks): resolve PATH-dependent bare python3 false-deny (#1697, #1581) - #1701

Merged
tvna merged 9 commits into
mainfrom
claude/fix-python-path-resolution-q2e3pv
Sep 3, 2026
Merged

fix(hooks): resolve PATH-dependent bare python3 false-deny (#1697, #1581)#1701
tvna merged 9 commits into
mainfrom
claude/fix-python-path-resolution-q2e3pv

Conversation

@tvna

@tvna tvna commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a PATH-dependent bare python3 false-deny in
hooks/check-pr-skill-audit-disclosure.sh's precondition probe (issue
#1697, a regression from #1566/PR #1675) and extends the same
deterministic-interpreter fix to the other nine hooks/*.sh bare-python3
call sites, closing out #1581's own residual scope on this defect class.
Also hard-fails a durable gate that previously could not have caught
this.

Facts

Assumptions

  • The nine other call sites' preventive fix is worth doing now rather
    than deferring (see Scope decision below) -- a judgment call, not a
    fact.
  • A consumer plugin install of this repository (only skills/ is
    currently deployed, per docs/repository-layout.md, with hooks/
    deployment stated there as planned for a future release) genuinely
    carries no uv toolchain or lockfile; I did not test an actual
    consumer install, only inferred this from that doc and from
    hooks/check-issue-acm-disclosure.sh's own existing comment making the
    same claim.

Scope decision: all 10 call sites in one PR (not split with #1581)

Issue #1697 asked me to decide and state reasoning rather than pick
silently. Chose to fix all ten hooks/*.sh bare-python3 call sites in
this one PR, and to close out #1581 alongside #1697, because:

  1. All ten are the same defect class (PATH-dependent interpreter
    resolution) -- fixing nine of them now, with the pattern and tests
    already built for the tenth, costs little extra.
  2. Each of the nine gets a command -v uv + lockfile-gated fallback to
    bare python3 (not an unconditional uv run), so none of them can
    newly break a consumer plugin install that has no uv toolchain --
    the same fail-safe direction hooks/check-post-review-obligation-tracker.sh
    and hooks/check-stop-review-obligation.sh already use for a missing
    python3 itself.
  3. gate-proposal: retro #1576 repair 1: PATH-dependent pre-push hook interpreter #1581's own stated target was already resolved by PR fix(hooks): route local-preflight through uv run, not bare python3 #1486 before
    gate-proposal: retro #1576 repair 1: PATH-dependent pre-push hook interpreter #1581 was even filed (see Facts) -- so gate-proposal: retro #1576 repair 1: PATH-dependent pre-push hook interpreter #1581's only remaining live
    value was the same defect-class awareness gate-proposal: PATH-dependent bare python3 in check-pr-skill-audit-disclosure.sh's precondition probe blocks create_pull_request in some sessions #1697 already carries
    forward. Closing it here avoids leaving a stale, already-resolved
    issue open indefinitely.

Risk / blast radius

  • Primary fix (tier-1 block in check-pr-skill-audit-disclosure.sh):
    scoped to a block that only runs when .github/scripts/ is present
    (this repository's own dev checkout) -- never reaches a consumer
    install.
  • The other 9 call sites + tier-2: each has a fallback to the
    pre-existing bare-python3 behavior when uv/lockfile are absent, so
    a consumer install with no uv toolchain is unaffected. If the fallback
    condition itself has a bug, the worst case is falling back to the
    pre-existing behavior (unchanged), not a new failure mode.
  • Gate hard-fail promotion: could newly block a future PR that adds a
    bare-python3 hooks/*.sh call site of a .github/scripts/*.py or a
    registered third-party-dependent hooks/*.py target -- this is the
    intended effect (issue gate-proposal: PATH-dependent bare python3 in check-pr-skill-audit-disclosure.sh's precondition probe blocks create_pull_request in some sessions #1697 item 3).

Rollback

git revert the commits on this branch. No schema, data, or
external-service change; a plain revert restores the exact pre-PR
behavior (including the original bug in check-pr-skill-audit-disclosure.sh).

Verification

Restating issue #1697's own re-verified Acceptance Criteria Map:

Criterion Interpretation Planned ops Proof method Residual risk
check-pr-skill-audit-disclosure.sh's precondition probe resolves the same interpreter regardless of the calling context's own PATH Change its python3 "$precondition_script" invocation (and the probe subprocess's own default inside gitapex_check_python_precondition.py) to uv run --frozen python3 (tier-1 gate invocation too), and default is_importable()'s own python argument to sys.executable instead of a fresh PATH lookup Edited hooks/check-pr-skill-audit-disclosure.sh (tier-1 precondition-probe and full_gate invocations); edited is_importable()/find_missing_modules() in hooks/gitapex_check_python_precondition.py Live reproduction via git worktree at the pre-fix commit: same ambient PATH, pre-fix denies (exit 2), post-fix allows (exit 0); plus new unit tests and the existing shell-level regression suite, all passing A .venv that itself lacks the required package still correctly denies -- this fix narrows which interpreter is resolved, not the precondition's own pass/fail logic
Every other hooks/*.sh bare-python3 call site gets the same deterministic-interpreter treatment uv run --frozen python3 when uv is on PATH and this checkout has its own pyproject.toml/uv.lock, falling back to bare python3 otherwise Edited all nine remaining hooks/*.sh files plus check-pr-skill-audit-disclosure.sh's tier-2 call site, each with a command -v uv+lockfile-gated fallback Existing shell-level regression suite passing; live-verified the fallback still exits 0 with uv removed from PATH on multiple hooks All nine plus tier-2 are stdlib-only today, so this is a preventive/consistency fix, not a currently-observable-failure fix, for those -- disclosed here rather than left implicit
The precondition probe's own subprocess default (is_importable(..., python="python3")) is also fixed Default to sys.executable, falling back to the literal "python3" only if sys.executable is itself empty (documented CPython edge case) Edited is_importable()/find_missing_modules() signatures in hooks/gitapex_check_python_precondition.py New unit tests intercepting subprocess.run's own argv to assert the default equals sys.executable, the empty-fallback, and that an explicit python= still overrides the default None identified -- sys.executable is the process's own known-good interpreter path by construction

Commands run (re-verified against the current head):

uv run --frozen python3 -m pytest -q --no-cov          # 8005 passed, 1 pre-existing/unrelated
uv run --frozen ruff check .                            # All checks passed
uv run --frozen mypy                                    # Success: no issues found
uv run --frozen python3 .github/scripts/gitapex_gate_bare_python3_invocation.py   # exit 0

Skill audit evidence

  • checker-script-adversarial-review: RAN -- against
    .github/scripts/gitapex_gate_bare_python3_invocation.py's own new
    load_python_dependent_hook_script_names/_scan_hook changes, across
    three rounds as findings surfaced (see Merge gate section below):
    decoy hooks/*.py names as both a substring-prefix and a
    substring-suffix of a registered target, the python3_cmd=(...)
    array-fallback shape confirmed NOT misdetected as a new bare
    invocation, a malformed/missing .gitapex/ssot.json, and a single
    malformed gates entry naming a real hooks/*.py target.
  • deterministic-gate-quality: RAN -- iteratively, as the independent
    review below progressed, read against
    skills/evaluating-deterministic-gate-quality/references/dimensions.md
    with dimension 15 (fail-closed default) as the focus throughout. Two
    genuine dimension-15 violations were found and fixed during this PR's
    own lifecycle (see Merge gate section below for the full account)
    rather than merely disclosed as accepted risk.
  • defeat-test-disclosure: RAN -- the defeat-case tests cited above are
    bundled in tests/test_gitapex_gate_bare_python3_invocation.py and
    tests/test_gitapex_gate_bare_python3_invocation_properties.py.

Checklist

  • Tests pass locally
  • Docs updated if behavior changed (no user-facing docs describe this internal hook wiring; an ADR was added -- see below)
  • Issue number cited in every commit
  • N/A: no SKILL.md/design-doc/security-relevant-skill change in this PR
  • N/A: no evals/*/split.md change in this PR
  • N/A: no SKILL.md Stop-boundary/branch change in this PR

Architecture decision record

docs/adr/0003-prefer-uv-resolved-python3-in-hooks-scripts.md -- retrofit
ADR recording this PR's own uv-preferred/bare-python3-fallback decision.
Approved by @tvna, 2026-09-03.

Merge gate: independent review

Outer layer (GitHub-native reviewer): requested
copilot-pull-request-reviewer[bot] via request_copilot_review twice
(once earlier in this PR's lifecycle, once against the current head) --
no review was returned either time within this session's own wait
window. The repository's other two configured checks, Devin Review
and CodeRabbit, both report as skipped by their own configuration
("trial expired and no credits remaining"; "manual review required for
this OSS repository"). This layer did not run.

Inner layer (reviewing-an-artifact, deferred at its own Step 0 to
evaluating-deterministic-gate-quality since this PR's diff is almost
entirely deterministic-gate/hook files, not ordinary application code):
run via a fresh, isolated subagent dispatch (no prior context on this
diff) across three rounds as the branch's head evolved:

  • Round 1 (against commit faab3c1): found 2 confirmed defects in
    .github/scripts/gitapex_gate_bare_python3_invocation.py --
    (a) a dimension-15 fail-closed violation: load_python_dependent_hook_script_names
    silently degraded to "nothing registered" instead of failing closed on
    a missing/unreadable/malformed .gitapex/ssot.json, live-reproduced
    (a crafted malformed registry alongside a real bare invocation of a
    registered target produced a false "clean" exit 0); (b) a missing
    path-boundary anchor in the registered-target regex, letting an
    unregistered file whose name merely ends with a registered name as a
    substring false-positive. Both fixed and pushed.
  • Round 2 (against commit f6bed27's predecessor, re-verifying both
    round-1 fixes live plus a full-diff re-review): confirmed both fixes
    hold in both directions, found a third, narrower mirror of finding
    (a) -- a single gates entry naming a hooks/*.py script but with a
    malformed preconditions/requires_python_packages field also
    silently skipped instead of failing closed, live-reproduced the same
    way. Fixed and pushed (commit f6bed27).
  • Round 3 (re-verification against f6bed27 after the round-2 fix,
    plus an ADR factual-accuracy pass): confirmed the round-2 fix live in
    both directions (malformed shapes now return None/hard-fail;
    legitimate no-op shapes still correctly contribute nothing, no
    overcorrection), and found 3 factual errors in the newly-added ADR
    (docs/adr/0003) -- a stale "not yet fixed" Consequence describing an
    already-fixed bug, a 9-vs-10 call-site count inconsistency between two
    of the ADR's own sections, and a present-tense claim about hooks/
    deployment that docs/repository-layout.md (the doc the ADR itself
    cites) actually states as future/planned. All three corrected and
    pushed. This round's own verdict: "the gate code itself ... is now
    well-formed and well-placed for this defect class."

No further findings against the current head (commit 99ed17d). Per
drafting-a-pr-to-merge's own Step 8 disclosure requirement: this
recorded verdict is disclosure for a human reader, not a self-certifying
signal for an automated downstream consumer.

Independent review verdict

  • Verdict: CLEAN
  • Verified commit: 99ed17d

Related Issue

Closes #1697
Closes #1581


Generated by Claude Code

…rministically

hooks/check-pr-skill-audit-disclosure.sh's tier-1 block invoked its
precondition probe and the skill-audit-disclosure gate script as bare
python3, which resolves from the calling PreToolUse hook's own ambient
PATH rather than this checkout's uv-managed .venv. When that PATH lacks
the venv (confirmed live in this environment), the probe reports a
genuinely-installed third-party package (pydantic) as missing and
false-denies create_pull_request/update_pull_request.

Route both invocations through uv run --frozen python3 (this block only
ever runs inside a dev checkout, guarded by .github/ presence). Also
route the tier-2 (SKILL.md-only) check_script invocation the same way,
with a command -v uv + lockfile-gated fallback to bare python3, since
that path also runs in a consumer plugin install with no uv toolchain.

Also default is_importable()'s own python argument to sys.executable
instead of a second, independent PATH lookup, so the probe always
reflects whichever interpreter actually launched it rather than
re-deriving a possibly-different one from PATH.

Live-reproduced via a git worktree at the pre-fix commit: under this
session's own ambient PATH (uv present, .venv/bin absent), the original
code denies (exit 2, "python3 cannot import: pydantic") and the fixed
code allows (exit 0).

Refs #1697, #1581

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
…fallback

Extends #1697's fix pattern to the nine other hooks/*.sh bare-python3
call sites (check-bash-safety.sh x2, check-issue-acm-disclosure.sh,
check-post-review-obligation-tracker.sh, check-post-write-provenance.sh,
check-pr-duplicate-issue.sh, check-pr-issue-acm-disclosure.sh,
check-pr-title-convention.sh, check-stop-review-obligation.sh) plus
check-pr-skill-audit-disclosure.sh's own tier-2 (SKILL.md-only) call
site, for consistency with the same PATH-nondeterminism class #1697's
own precondition-probe fix closes.

All ten scripts these call sites invoke are stdlib-only today (verified
by inspecting every import statement), so this is a preventive or
consistency fix rather than a currently-observable-failure fix for these
nine plus tier-2. Each call site prefers uv run --frozen python3 when uv
is on PATH and this checkout has its own pyproject.toml/uv.lock, falling
back to a bare python3 otherwise. hooks/ is deployed to consumer plugin
installs (see docs/repository-layout.md), which carry neither a uv
toolchain nor a lockfile, so an unconditional uv run would have newly
broken every such install; this fallback keeps the pre-existing
behavior unchanged there.

Also closes out #1581's own residual scope on this fix pattern (its
stated target, .pre-commit-config.yaml's local-preflight entry, was
already fixed by PR #1486/#1485 -- see the comment on #1581 for the
live-verified timeline).

Refs #1697, #1581

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
…*.py target

gitapex_gate_bare_python3_invocation.py previously only WARNED
(report-only, never affected exit code) on a hooks/*.sh bare-python3
invocation of a .github/scripts/*.py target, and never inspected
hooks/*.py targets at all -- so it could not have caught #1697's own
root cause, a bare invocation of hooks/gitapex_check_python_precondition.py.

Promotes the .github/scripts/*.py case from WARNING to HARD-FAIL, and
adds a new HARD-FAIL case for a hooks/*.py file registered in
.gitapex/ssot.json under a gate whose own
preconditions.requires_python_packages is non-empty (a hooks/*.py file
that genuinely needs a third-party-dependent, deterministically-resolved
interpreter, not every hooks/*.py sibling indiscriminately -- an
unregistered hooks/*.py target stays bare-invoked by design, per
docs/repository-layout.md).

Also registers hooks/gitapex_check_python_precondition.py under the
skill-audit-disclosure gate's own script list in .gitapex/ssot.json,
since that gate is the one declaring the pydantic precondition this
script exists to probe, and it was missing from that list.

Live-verified this gate would have caught #1697's own defect before
merge: run against a git-worktree checkout of the pre-fix commit's
hooks/ directory with the fixed ssot.json, it correctly flags all three
original bare invocations in check-pr-skill-audit-disclosure.sh
(precondition_script, full_gate, and check_script).

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
…hook directly

local-preflight's detection-logic-property-coverage and
function-body-test-coverage gates flagged the previous commit's own
gitapex_gate_bare_python3_invocation.py changes: the new
load_python_dependent_hook_script_names string-comparison call site had
no hypothesis @given property test, and the changed _scan_hook body had
no test in the same diff mentioning it by name (existing tests only
called it through find_hooks_shell_indirected_invocations's wrapper).

Adds direct _scan_hook unit tests for a registered vs. unregistered
hooks/*.py target, plus hypothesis property tests for
load_python_dependent_hook_script_names covering: a required-package
gate contributes its own hooks/*.py basename (never a sibling
.github/scripts/*.py entry on the same gate), a gate with no
preconditions never contributes one, and an empty
requires_python_packages list is treated the same as absent.

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 01:14 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: f729fc21-3466-489b-ba38-5ac4d0a88ad2


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.66%. Comparing base (f274bda) to head (99ed17d).
⚠️ Report is 10 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1701   +/-   ##
=======================================
  Coverage   99.66%   99.66%           
=======================================
  Files         152      152           
  Lines       24725    24792   +67     
  Branches     2967     2981   +14     
=======================================
+ Hits        24643    24710   +67     
  Misses         82       82           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov/patch flagged PR #1701's head commit at 98.36% patch coverage
(target 99.66%), missing gitapex_gate_bare_python3_invocation.py:310 --
load_python_dependent_hook_script_names's own early-return when
.gitapex/ssot.json's "gates" key is present but not a list (a malformed
registry shape distinct from the already-covered missing-file,
undecodable, and non-mapping-top-level cases).

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 01:22 — with GitHub Actions Active
…eview

An independent evaluating-deterministic-gate-quality review of PR #1701
(dispatched via a fresh, isolated subagent per that skill's own
Subagent-dispatch rule) live-confirmed two real defects in this PR's own
gitapex_gate_bare_python3_invocation.py changes:

1. Dimension-15 (fail-closed) violation: load_python_dependent_hook_script_names
   silently degraded to an empty frozenset on a missing/unreadable/malformed
   .gitapex/ssot.json, which find_hooks_shell_indirected_invocations then
   read as "nothing registered" -- so a crafted malformed ssot.json
   alongside a real bare python3 invocation of a registered hooks/*.py
   target produced a false "No ... bare invocations found" and exit 0.
   This broke the same file's own established convention
   (find_bare_invocations/_scan_workflow already treat an unreadable
   input as a "cannot verify" finding forcing exit_code=1). Fixed:
   load_python_dependent_hook_script_names now returns None (not an
   empty frozenset) on any unreadable/malformed registry; main() treats
   None as a hard failure while still running the .github/scripts/*.py
   scan against an empty hooks/*.py scope, matching find_bare_invocations's
   own "still scan what you can, but report the inability to verify"
   precedent. Live-verified: a malformed ssot.json now exits 1 with
   "Could not read or parse ..." instead of the prior false-clean exit 0.

2. Missing path-boundary anchor: the registered-hooks/*.py-target regex
   anchored only on a trailing "$", so an unregistered file whose own
   name merely ENDS with a registered name as a substring (e.g.
   "my_other_gitapex_check_python_precondition.py" against registered
   "gitapex_check_python_precondition.py") would false-positive. Fixed
   by anchoring on "(?:^|/)" before the name, requiring a real
   path-component boundary. Live-verified the decoy no longer flags.

Both fixes covered by new unit tests (including the exact decoy/malformed
inputs above) plus updated existing tests whose expected return value
changed from frozenset() to None for the four genuinely-malformed-registry
cases (a well-formed-but-empty registry, or one with no matching gates,
still correctly returns frozenset()).

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 01:47 — with GitHub Actions Active
Retrofit ADR for PR #1701's own decision (hooks/*.sh companion-script
invocations prefer `uv run --frozen python3` when this checkout owns a
uv toolchain and lockfile, falling back to the pre-existing bare
`python3` otherwise) that fixed issue #1697's PATH-dependent false-deny
and closed out issue #1581's residual scope on the same defect class.
Approved by tvna, 2026-09-03.

Refs #1697, #1581

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 03:17 — with GitHub Actions Active
Second independent review of this branch found a narrower mirror of the
already-fixed whole-file fail-open bug: load_python_dependent_hook_script_names
returned an empty frozenset (a legitimate-looking "no additional
targets") rather than failing closed when a single `gates` entry DID
name a `hooks/*.py` script but its own `preconditions` or
`requires_python_packages` field was present and malformed (e.g. a
string instead of a mapping/list) -- live-reproduced: a well-formed
ssot.json with one such corrupted entry, alongside a real bare
invocation of the registered target, produced a false "clean" exit 0.

Fixed by checking each gate's own `script` list for a hooks/*.py target
first; only once one is present does a malformed `preconditions`/
`requires_python_packages` shape return None (fail closed) instead of
being skipped. A gate that legitimately omits preconditions, or that
names no hooks/*.py script at all, is unaffected and still contributes
nothing, as before.

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 03:21 — with GitHub Actions Active
A second independent review of PR #1701 (dispatched to re-verify the
per-gate fail-open fix, commit f6bed27) also checked ADR 0003 for
factual accuracy against the actual code/docs and found three errors:

1. "Not yet fixed as of this ADR's writing" / "Unknown, pending a
   follow-up fix" (the per-gate fail-open Consequence) was already
   stale -- f6bed27, committed on this same branch shortly after the
   ADR was first drafted, had already fixed it. Updated to state the
   fix and cite the commit; the reviewer live re-verified the fix holds.
2. Decision Outcome said "the other nine call sites" where Consequences
   correctly said "ten call sites (nine files, check-bash-safety.sh
   twice)" -- an internal inconsistency. Independently re-counted via
   `grep -c 'python3_cmd\[@\]' hooks/*.sh`: 10 occurrences across 9
   files, confirming "ten" is correct. Decision Outcome corrected to
   match.
3. The ADR stated hooks/ deployment to a consumer plugin install as
   current fact ("only skills/ and hooks/ are ever deployed... hooks/
   is entirely on the deployed side"), but docs/repository-layout.md
   (the doc it cites) describes hooks/ deployment as future/planned
   ("and, in the future, hooks from hooks/"; "and, later, hooks"), not
   yet shipped. Corrected Context and the duplication Consequence to
   describe hooks/ deployment as planned rather than current, and
   Context's own risk framing to "as soon as hooks/ deployment ships"
   rather than an already-present break.

No change to the Decision Outcome (uv-preferred python3 resolution with
a bare-python3 fallback) or Status -- these are factual corrections to
an already-Accepted record, not a re-litigation of the decision itself.

Refs #1697

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGHXhvkGRDFxGBARLbmh81
@tvna
tvna deployed to ruleset-verify September 3, 2026 03:31 — with GitHub Actions Active
@tvna
tvna marked this pull request as draft September 3, 2026 03:47
@tvna
tvna marked this pull request as ready for review September 3, 2026 03:50
@tvna
tvna merged commit 2ce6c2f into main Sep 3, 2026
48 of 51 checks passed
@tvna
tvna deleted the claude/fix-python-path-resolution-q2e3pv branch September 3, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants