From 87bfb9426eae962d896dee3fa59c6dca8f6172d5 Mon Sep 17 00:00:00 2001 From: Yuan Li Date: Sun, 27 Sep 2026 23:44:48 +0800 Subject: [PATCH] fix(pm): isolate per-package pin failures instead of aborting the run _apply_pins let any exception from _pin_artifacts propagate: one broken pin - a package's resolution bug (gh's 3-part target split) or one target's upstream pool skew (Termux rolling pool 404 while nodejs.org is ahead) - aborted the entire run, blocked every unrelated package, and left lockfile.save() unreached, so any failure meant zero progress. Catch per package, pin everything that succeeds, install the pinned set, print a 'pinned X, failed Y' summary, and exit non-zero when anything failed. A run where every pin fails still leaves the lockfile untouched. Fixes #125386 --- pm/cli.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/pm/cli.py b/pm/cli.py index 7134d3e8de..471f9a6e76 100644 --- a/pm/cli.py +++ b/pm/cli.py @@ -589,15 +589,33 @@ def _apply_pins(changed: list, lockfile) -> int: if not changed: print("pm update: nothing to update") return 0 + pinned: list[str] = [] + failed: list[str] = [] for d in changed: package = get_package(d.name) - artifacts = _pin_artifacts(package, d, lockfile.pinned_artifacts(d.name)) + try: + artifacts = _pin_artifacts(package, d, lockfile.pinned_artifacts(d.name)) + except Exception as e: + # One broken pin — a package's resolution bug or one target's upstream + # pool skew (rolling Termux pool 404 while nodejs.org is ahead) — must + # not zero out the whole run. Pin the rest, report the failure (#125386). + print(f"✗ {d.name} pin failed: {e}") + failed.append(d.name) + continue lockfile.set_pin(d.name, d.version, artifacts) + pinned.append(d.name) print(f"✓ {d.name} pinned {d.locked or '—'} → {d.version}") - lockfile.save() - if _install_names([d.name for d in changed]): + if not pinned: + print("pm update: every pin failed; lockfile untouched") return 1 - return 0 if _sync_venv_step() else 1 + lockfile.save() + if _install_names(pinned): + return 1 + rc = 0 if _sync_venv_step() else 1 + if failed: + print(f"pm update: pinned {len(pinned)}, failed {len(failed)}: {', '.join(failed)}") + return 1 + return rc def _refresh_uv_lock() -> int: