From c6e8c05438e3c05051d6e2466ff0f4cda0532ba4 Mon Sep 17 00:00:00 2001 From: "pullapprove5-fix[bot]" <4489445+pullapprove5-fix[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 14:45:05 +0000 Subject: [PATCH] Fix: oxlint/oxfmt binaries are fetched from github.com, unlike ruff/ty which resolve via PyPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduced the finding by installing uv (absent in this sandbox) and simulating a GitHub-blocked-but-PyPI-reachable environment (HTTPS_PROXY/HTTP_PROXY pointed at a closed port, since this container's real network could actually reach github.com). `uv run plain-code fix .` crashed with a raw httpx.ConnectError traceback when oxc's install step tried to hit api.github.com/github.com, and — unlike `plain code check`, which already has a `--skip-oxc` flag — `plain code fix` had no way to skip it and degrade gracefully.\n\nFixed plain-code/plain/code/oxc.py by wrapping the two GitHub-hitting calls (`get_latest_version`'s `httpx.get`, `download`'s `httpx.stream`) in try/except httpx.HTTPError, re-raising as `click.ClickException` with an actionable message pointing at `--skip-oxc` or pinning a version — Click renders just that message, no traceback. Added a `--skip-oxc` flag to the `fix` command in cli.py (mirroring the one `check` already had) so there's an actual escape hatch, not just a nicer error.\n\nRan the same simulated-block repro before/after: before, a full httpx traceback; after, a one-line `Error: Couldn't download oxlint from github.com (...). ... pass --skip-oxc.` and exit 1, with `--skip-oxc` now succeeding (exit 0) without any network access. Ran `uv run plain-code check .` and `uv run plain-code fix plain-code` unblocked afterward — ruff, ty, oxlint/oxfmt, and annotations all pass, confirming the normal path (including a real oxc download) still works. plain-code has no pytest suite (not in scripts/test's package list, no tests/ dir), so this check/fix run is the package's own validation.\n\nDid not pursue the finding's other suggested option (mirroring oxc binaries through PyPI wheels) — that's a much larger publishing/design change, not a minimal fix. --- plain-code/plain/code/cli.py | 9 ++++-- plain-code/plain/code/oxc.py | 54 +++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/plain-code/plain/code/cli.py b/plain-code/plain/code/cli.py index 69789df79b..16473d11e5 100644 --- a/plain-code/plain/code/cli.py +++ b/plain-code/plain/code/cli.py @@ -282,8 +282,13 @@ def _print_annotations_json(result: AnnotationResult) -> None: @click.argument("paths", nargs=-1) @click.option("--unsafe-fixes", is_flag=True, help="Apply ruff unsafe fixes") @click.option("--add-noqa", is_flag=True, help="Add noqa comments to suppress errors") +@click.option("--skip-oxc", is_flag=True, help="Skip oxlint and oxfmt") def fix( - ctx: click.Context, paths: tuple[str, ...], unsafe_fixes: bool, add_noqa: bool + ctx: click.Context, + paths: tuple[str, ...], + unsafe_fixes: bool, + add_noqa: bool, + skip_oxc: bool, ) -> None: """Fix formatting and linting issues""" if not paths: @@ -327,7 +332,7 @@ def fix( if result.returncode != 0: sys.exit(result.returncode) - if other_paths and config.get("oxc", {}).get("enabled", True): + if not skip_oxc and other_paths and config.get("oxc", {}).get("enabled", True): oxlint = OxcTool("oxlint") oxfmt = OxcTool("oxfmt") diff --git a/plain-code/plain/code/oxc.py b/plain-code/plain/code/oxc.py index 9e284bdb78..bfebe14c66 100644 --- a/plain-code/plain/code/oxc.py +++ b/plain-code/plain/code/oxc.py @@ -118,13 +118,21 @@ def detect_platform_slug(self) -> str: @staticmethod def get_latest_version() -> str: """Find the latest apps_v release tag via the GitHub API.""" - resp = httpx.get( - "https://api.github.com/repos/oxc-project/oxc/releases", - params={"per_page": 20}, - headers={"Accept": "application/vnd.github+json"}, - follow_redirects=True, - ) - resp.raise_for_status() + try: + resp = httpx.get( + "https://api.github.com/repos/oxc-project/oxc/releases", + params={"per_page": 20}, + headers={"Accept": "application/vnd.github+json"}, + follow_redirects=True, + ) + resp.raise_for_status() + except httpx.HTTPError as e: + raise click.ClickException( + "Couldn't reach github.com to look up the latest oxlint/oxfmt " + f"release ({e}). If this environment can't reach GitHub, pin a " + "version in pyproject.toml under [tool.plain.code.oxc], or pass " + "--skip-oxc." + ) from e for release in resp.json(): tag = release["tag_name"] if tag.startswith(TAG_PREFIX): @@ -143,21 +151,27 @@ def download(self, version: str = "") -> str: # Download into memory for extraction data = io.BytesIO() - with httpx.stream("GET", url, follow_redirects=True) as resp: - resp.raise_for_status() - total = int(resp.headers.get("Content-Length", 0)) - if total: - with click.progressbar( - length=total, - label=f"Downloading {self.name}", - width=0, - ) as bar: + try: + with httpx.stream("GET", url, follow_redirects=True) as resp: + resp.raise_for_status() + total = int(resp.headers.get("Content-Length", 0)) + if total: + with click.progressbar( + length=total, + label=f"Downloading {self.name}", + width=0, + ) as bar: + for chunk in resp.iter_bytes(chunk_size=1024 * 1024): + data.write(chunk) + bar.update(len(chunk)) + else: for chunk in resp.iter_bytes(chunk_size=1024 * 1024): data.write(chunk) - bar.update(len(chunk)) - else: - for chunk in resp.iter_bytes(chunk_size=1024 * 1024): - data.write(chunk) + except httpx.HTTPError as e: + raise click.ClickException( + f"Couldn't download {self.name} from github.com ({e}). If this " + "environment can't reach GitHub, pass --skip-oxc." + ) from e data.seek(0)