fix(install): make Windows pip fallback resilient to stderr under ErrorActionPreference=Stop (closes #1874)#1876
Conversation
Prevent native pip stderr from becoming a terminating PowerShell error while preserving exit-code based failure handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens the Windows install.ps1 pip fallback against PowerShell 5.1 cases where native-command stderr can be promoted to a terminating error when $ErrorActionPreference = "Stop", ensuring the fallback still relies on pip’s exit code as intended.
Changes:
- Scope
$ErrorActionPreference = "Continue"to the native pip invocation insideInstall-ViaPip, restoring the previous value in afinallyblock. - Add a unit regression test that asserts the guard is present and ordered correctly around both pip invocation variants.
Show a summary per file
| File | Description |
|---|---|
| install.ps1 | Wraps native pip execution with a scoped $ErrorActionPreference override and restoration to avoid stderr-triggered termination. |
| tests/unit/test_enterprise_bootstrap_installers.py | Adds a regression test validating the new guard exists and is positioned correctly within Install-ViaPip. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 1
| text = _read_repo_file("install.ps1") | ||
| body = text.split("function Install-ViaPip {", 1)[1].split( | ||
| "function Write-ManualInstallHelp {", 1 | ||
| )[0] |
Record the enterprise PowerShell compatibility fix surfaced by the review panel so the release notes make the Windows installer hardening discoverable. Addresses CEO follow-up from apm-review-panel for PR #1876. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 0 | 1 | Clean save/restore guard around native pip invocation; no Python architecture surface touched. Ship. |
| CLI Logging Expert | 0 | 0 | 1 | The guard preserves proper exit-code diagnostics by preventing pip stderr noise from becoming a false terminating error. |
| DevX UX Expert | 0 | 0 | 2 | ErrorActionPreference guard is correct UX for pip stderr; no regression in the install command surface. |
| Supply Chain Security Expert | 0 | 0 | 0 | No supply-chain or security surface affected; change is purely PowerShell error-preference scoping around an unchanged pip invocation. |
| OSS Growth Hacker | 0 | 0 | 1 | Enterprise-friendly installer hardening removes a silent adoption blocker; release-note visibility was folded. |
| Test Coverage Expert | 0 | 0 | 0 | ErrorActionPreference guard in install.ps1 is defended by a static ordering-assertion test; coverage is adequate for this surface. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 3 follow-ups
- [Python Architect] Consider regex-based assertion in the static test to decouple from line ordering -- reduces future maintenance churn if the ErrorActionPreference block is refactored; current form is correct and passing.
- [DevX UX] Add mirror URL redaction in manual-install help output and enterprise-mode success breadcrumb -- polish for the enterprise install experience; outside this stderr terminating-error fix.
- [CLI Logging] Align Write-Host usage in install.ps1 with a consistent output helper style -- low-priority consistency pass; current output is functional and correct.
Recommendation
Merge as-is. The fix is minimal, mutation-validated, CI-green, and closes a reported enterprise regression. The remaining follow-ups are polish or hardening outside this PR's stated scope; track them separately if desired.
Folded in this run
- (panel) Add CHANGELOG entry for enterprise PowerShell compatibility -- resolved in 0ec5927.
Copilot signals reviewed
- Copilot review fetched twice; no inline comments were present to classify.
Deferred (out-of-scope follow-ups)
- (panel) Redact embedded credentials in
Write-ManualInstallHelpmirror URL output -- scope boundary: PR scope is the ErrorActionPreference pip fallback failure; this is a separate manual-help credential-redaction hardening pass. - (panel) Add enterprise mirror-mode success breadcrumb -- scope boundary: PR scope is a false terminating-error bug; this is broader installer UX messaging.
- (panel) Refactor static test to regex-based structural matching -- scope boundary: current regression trap is passing and mutation-proven; this is future test-maintenance polish.
- (panel) Align
Write-Hostremediation style with output helpers -- scope boundary: PR does not change remediation output styling.
Regression-trap evidence (mutation-break gate)
tests/unit/test_enterprise_bootstrap_installers.py::test_windows_pip_fallback_scopes_native_stderr_error_action_guard-- deleted$ErrorActionPreference = "Continue"; test FAILED as expected; guard restored.
Lint contract
uv run --extra dev ruff check src/ tests/ and uv run --extra dev ruff format --check src/ tests/ both silent. Full CI lint equivalent also passed locally (YAML I/O guard, file-length guard, raw relative_to guard, pylint R0801, auth-signals).
CI
GitHub checks green on 0ec592773b80a3425a507dd90a68b0bc54eb6f61: Lint, Build & Test Shard 1/2, gate, APM Self-Check, PR Binary Smoke, Coverage Combine, CodeQL, NOTICE Drift Check, Analyze (python/actions), and license/cla all passed (after 0 CI fix iterations).
Mergeability status
Captured from gh pr view 1876 --json mergeable,mergeStateStatus,statusCheckRollup immediately after the last push of this run.
| PR | head SHA | CEO stance | iters | folds | defers | Copilot rounds | CI | mergeable | mergeStateStatus | notes |
|---|---|---|---|---|---|---|---|---|---|---|
| #1876 | 0ec5927 |
ship_now | 1 | 1 | 4 | 2 | green | MERGEABLE | BLOCKED | awaiting maintainer review |
Convergence
1 outer iteration; 2 Copilot rounds. Final panel verdict: ship_now.
Ready for maintainer review.
Full per-persona findings
Python Architect
- [nit] Test asserts string ordering but is brittle to incidental refactors at
tests/unit/test_enterprise_bootstrap_installers.py:183
The test uses body.index() ordering of raw string literals. This is acceptable for a regression trap on a shell script but worth noting as a maintenance cost; a regex-based structural match would be more robust.
CLI Logging Expert
- [nit] Fail-closed remediation line uses raw Write-Host instead of Write-Info at
install.ps1:263
The adjacent guidance line uses bare Write-Host. This is internally consistent with existing installer patterns and is not material to the PR scope.
DevX UX Expert
- [nit] Manual install help leaks raw mirror URL without redaction at
install.ps1:310
Write-ManualInstallHelp prints$pypiIndexUrlverbatim if the mirror URL contains embedded credentials. This is pre-existing and outside the ErrorActionPreference fix.
Suggested: Use Redact-UrlCredentials in a separate hardening PR. - [nit] No feedback when APM_NO_DIRECT_FALLBACK is active but everything succeeds
A positive enterprise mirror mode breadcrumb could reassure admins, but this is a broader installer UX enhancement outside the stderr terminating-error fix.
Supply Chain Security Expert
No findings.
OSS Growth Hacker
- [nit] The fix is story-shaped but invisible; consider a CHANGELOG entry calling out enterprise PowerShell compatibility
Enterprise users running ErrorActionPreference=Stop are a high-value segment. This was folded into CHANGELOG.md.
Auth Expert -- inactive
Touched files are install.ps1 and tests/unit/test_enterprise_bootstrap_installers.py; no token management, credential resolution, host classification, git/HTTP auth headers, or remote-host fallback semantics changed.
Doc Writer -- inactive
No documentation-relevant surface touched before the folded CHANGELOG entry; no docs drift remains.
Test Coverage Expert
No findings.
Performance Expert -- inactive
Touched files are install.ps1 and tests/unit/test_enterprise_bootstrap_installers.py; no cache, transport, materialization, parallelism, benchmark, or performance claim changed.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
TL;DR
Make the Windows pip fallback ignore non-fatal native stderr as a PowerShell terminating-error source while preserving the existing pip exit-code handling.
Problem
With
$ErrorActionPreference = "Stop", PowerShell 5.1 can turn stderr redirected through2>&1from a native pip command into a terminatingNativeCommandError/System.Management.Automation.RemoteException. That happens before the installer checks$LASTEXITCODE, so pip notices or warnings can make a successful fallback look failed.Fix
Inside
Install-ViaPip, save the current$ErrorActionPreference, set it to"Continue"only while invoking native pip, then restore it infinally. The captured output and non-zero exit-code behavior stay unchanged.How to test
$HOME/.local/bin/uv run --extra dev pytest tests/unit/test_enterprise_bootstrap_installers.py -q$HOME/.local/bin/uv run --extra dev ruff check src/ tests/ --quiet$HOME/.local/bin/uv run --extra dev ruff format --check src/ tests/ --quietcloses #1874