Skip to content

feat(install): diagnose a blocked package index instead of retrying blindly - #439

Merged
Jason Robert (jrob5756) merged 3 commits into
mainfrom
feature/private-package-index-guidance
Aug 14, 2026
Merged

feat(install): diagnose a blocked package index instead of retrying blindly#439
Jason Robert (jrob5756) merged 3 commits into
mainfrom
feature/private-package-index-guidance

Conversation

@jrob5756

@jrob5756Jason Robert (jrob5756) commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

Installs fail on networks that block direct access to the public Python
package index — increasingly common on managed corporate devices, where a
policy requires all packages to come from an internal mirror.

uv tool install fails with a fetch/403/DNS-shaped error, and uv has
already exhausted its own retries by that point. So the install scripts
spent their full 2s/5s/10s backoff on a failure that cannot heal, and
install.ps1 then printed file-lock advice — including a suggestion to add
a Windows Defender exclusion, which is useless and actively misleading
for a network-policy block.

The user-visible experience was three silent retries followed by wrong
advice. Nothing named the actual cause, and nothing named the setting that
fixes it.

What changed

Classification. Both scripts now sort a failed install into one of four
kinds and respond to each differently:

FailureRetriesMessage
Definitive index block (uv gave up; 401/403/407/DNS/TLS)stops immediatelyhow to point uv at your index
Unreachable github.comfull schedulenames github.com, says the index is not the problem
Connection blip (connection reset, timeout)full scheduleindex guidance only if the run ends on it
Anything else (build error, hash mismatch, lock)full scheduleexisting advice, unchanged

The scripts also echo the active index at install time — with credentials in
the URL redacted — so "did my override apply?" is answerable from the
install log alone.

README.md — new Installing behind a proxy or private package index
section: uv (env var, uv.toml, named-index credentials, TLS inspection,
proxies) and pip/pipx separately, including the trap that uv does not read
pip's configuration
.

No vendor endpoint is hardcoded

Conductor is public OSS installed on many different networks. A baked-in
mirror would silently redirect every other user's dependency resolution, so
the index stays user-supplied configuration and the docs use a
<your-index-host> placeholder.

A static test enforces this by shape rather than by denylisting known
vendors — it matches any https://…/simple/ URL and requires it to be
either pypi.org or the placeholder. That catches a mirror the test's
author never heard of, and avoids naming anyone's internal infrastructure
inside a public repo.

Second commit: review fixes

A multi-agent review found four issues that were reproduced against real uv
0.11.30 and a faithful simulation of each script's retry loop before being
fixed:

  • A blocked github.com was reported as a blocked package index. uv
    words a failed git fetch with the same "failed to fetch" phrase it uses
    for the index, and the installer fetches Conductor itself from
    git+https://github.com/... — so github.com is on the primary path for
    every install. The user would set a perfectly correct UV_DEFAULT_INDEX,
    fail identically, and conclude the URL was wrong.
  • On Windows the index classifier was unreachable on a fresh install
    whose output also contained lock-shaped text. The lock fallback guarded on
    the result of the rename, and Move-ConductorToolDirAside returns
    $null when there is no existing tool dir — so the guard never flipped,
    the branch continued on all four attempts, and the run ended on the
    Defender advice this PR exists to avoid.
  • Classification read the accumulated log, so one transient blip
    anywhere in a run relabeled whatever the run finally failed on: a missing
    portaudio.h was reported as a blocked index.
  • The needles were too broad. Matching pypi.org alone reported a hash
    mismatch as an unreachable index — the worst available answer for a
    supply-chain signal. Bare host needles are gone; every needle now names a
    failure.

Also in that commit: credentials redacted in the echo; guidance adapts when
an override is already set rather than advising what the user already did;
the named-index form throughout so the documented UV_INDEX_<NAME>_*
credentials are actually usable; TLS-inspection and proxy remedies named; a
missing log no longer kills install.sh with a bare sed error; the
renamed-aside notice prints before any early exit; 401 matched; grep
pinned to LC_ALL=C; PowerShell lowercases invariantly; and the CHANGELOG
corrected (the Defender advice was never rescoped — Write-Err just exits
before it — and install.sh never had Defender advice to rescope).

Verification

Behavior was exercised end-to-end against a fake uv, including the Windows
path under PowerShell 7.4. install.sh and install.ps1 agree on all seven
scenarios:

Scenarioattemptsclassified as
Blocked index1index
Block first appears on attempt 22index
Lock + network, fresh install2index
Transient blip + build failure4generic
Unreachable git host4github.com
Hash mismatch4generic
Ordinary build failure4generic

Underlying uv behavior was confirmed against real uv 0.11.30 rather than
assumed: UV_DEFAULT_INDEX redirects resolution; a git+https://… install
routes build deps (packaging) through it too; the deprecated
UV_INDEX_URL still works; and an unreachable git host really does emit
failed to fetch, which is what made the misdiagnosis above possible.

Tests

  • _uv_shim.py gained seven modes and a POSIX shim (it was Windows-only).
  • Six behavioral tests: blocked index, ordinary failure (the negative
    direction), transient blip, late block, both classifiers matching, and a
    git-host failure.
  • Four static checks in the default make test suite.
  • Every fix was negative-controlled — reverting it makes its test fail.
    Two tests were themselves found to be vacuous during review and fixed: the
    guidance assertion was satisfied by the scripts' header comments with all
    guidance functions deleted, and the classifier assertion passed for a
    function that was defined but never called.

Full suite: 7064 passed; install-scripts 10 passed / 4 skipped; ruff and
ty clean; install.ps1 still ASCII / no BOM / LF.

Notes for reviewers

  • This makes the failure legible; it does not configure anything. Users
    on a restricted network still supply their own index — by design.
  • Publishing to PyPI would be complementary, not a replacement: it would
    remove the from-source build (and its build-backend fetches), but a
    blocked network still needs the mirror.

Draft for review of approach before polish.

Jason Robertand others added 2 commits August 14, 2026 12:49
…lindly
On networks that block direct access to the public Python package index --
increasingly common on managed corporate devices -- `uv tool install` fails
with a fetch/403/DNS-shaped error. uv has already exhausted its own retries
by that point, so the install scripts spent their 2s/5s/10s backoff on a
failure that cannot heal and then printed file-lock advice, including a
Windows Defender exclusion suggestion that is actively misleading for a
network-policy block.
Both scripts now classify the failure (`is_network_block_error` /
`Test-NetworkBlockError`), stop after the first attempt, and explain the
actual remedy: point uv at your organization's index with
`UV_DEFAULT_INDEX`. They also echo the active index at install time, so
"did my override apply?" is answerable from the install log alone, and the
Defender advice is now scoped to lock failures only.
The needles are deliberately generic and no mirror is hardcoded. Conductor
is public OSS installed on many different networks; a baked-in endpoint
would silently redirect every other user's dependency resolution. The index
stays user-supplied configuration.
Also documents the setup in the README, including the trap that uv does not
read pip's configuration -- so `pip config set global.index-url` alone has
no effect on the install scripts, `uv tool install`, or `conductor update`.
Tests: the `_uv_shim` gains a `network-always` mode and a POSIX shim, and a
behavioral test asserts exactly one uv attempt plus the guidance text. Four
static checks run in the default suite, including one that fails if either
script ever hardcodes an index URL (matched by shape, so it catches a mirror
the test's author never heard of) and one that keeps the scripts' docs link
from dangling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Follow-up to the blocked-index classifier, addressing findings from a
multi-agent code review. Four were reproduced against real uv 0.11.30 and
a faithful simulation of each script's retry loop before being fixed.
A blocked github.com was reported as a blocked package index. The installer
fetches Conductor itself from `git+https://github.com/...`, and uv words a
failed git fetch with the same "failed to fetch" phrase it uses for the
index. A user blocked from GitHub was told, in bold, that their package
index was the problem -- so they would set a perfectly correct
UV_DEFAULT_INDEX, fail identically, and conclude the URL was wrong. Git
failures are now classified separately and get their own message, and they
keep the retry schedule since uv does not retry git fetches internally.
On Windows the index classifier was unreachable on a fresh install whose
output also contained lock-shaped text. The lock fallback guarded on the
*result* of the rename, and `Move-ConductorToolDirAside` returns $null when
there is no existing tool dir -- so the guard never flipped, the branch
`continue`d on all four attempts, and the run ended on the Defender advice
this change exists to avoid. The guard now tracks the attempt.
Classification read the accumulated log, so one transient blip anywhere in
a run relabeled whatever the run finally failed on: a missing portaudio.h
was reported as a blocked index. It now reads only the most recent attempt.
The needles are split into two tiers. Only a definitive block (uv stating it
gave up, or a policy/DNS/TLS fact) short-circuits the retries; connection-level
failures still get the full schedule, since unlike a policy block a VPN blip
can genuinely heal. Bare host needles are gone -- matching `pypi.org` alone
reported a hash mismatch as an unreachable index, which is the worst
available answer for a supply-chain signal. 401 is matched, `grep` is pinned
to LC_ALL=C, and PowerShell lowercases invariantly.
Also: the index echo redacts credentials (an index URL is a documented place
to put them, and that line reaches every CI log); guidance adapts when an
override is already set, rather than advising the user to do what they have
already done; the named-index form is used throughout so the documented
UV_INDEX_<NAME>_* credentials are actually usable; TLS-inspection and proxy
remedies are named; a missing log no longer kills install.sh with a bare sed
error; and the renamed-aside notice prints before any early exit.
Tests: five new behavioral cases covering the negative direction (ordinary
failure still retries and shows no index guidance), a transient blip, a
block arriving on a later attempt, both classifiers matching at once, and a
git-host failure. Each was verified to fail when its fix is reverted. The
static test asserting the guidance mentions UV_DEFAULT_INDEX was vacuous --
the header comments satisfied it with every guidance function deleted -- so
it now scopes to the function body, and the classifier test checks the
function is called rather than merely defined. Shim env is cleared so a
developer's own UV_DEFAULT_INDEX cannot satisfy an assertion, and an unknown
shim mode fails loudly instead of falling through to a real network install.
CHANGELOG corrected: the Defender advice was never rescoped (Write-Err just
exits before it), and install.sh never had Defender advice to rescope.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jrob5756
Jason Robert (jrob5756) marked this pull request as ready for review August 14, 2026 19:59
…ge-index-guidance
# Conflicts:
#	CHANGELOG.md
#	install.sh
@jrob5756
Jason Robert (jrob5756) merged commit 992931d into mainAug 14, 2026
11 checks passed
@jrob5756
Jason Robert (jrob5756) deleted the feature/private-package-index-guidance branch August 14, 2026 20:53
Sign up for freeto 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.

1 participant

@jrob5756