Fall back to pip3 when pip is missing; clearer missing-binary error#100
Conversation
When `which::which("pip")` fails, exec resolution retries `pip3` — pip
is the one manager with a conventional alias. The missing-binary error
now names the binary and the fallback tried:
error: 'pip' not found on PATH (also tried 'pip3')
Exit 127 unchanged. The post-resolution exec-failure message names the
resolved path instead of the requested binary so it stays accurate when
the fallback was taken.
New hermetic e2e suite (tests/cli_exec_fallback.rs): a controlled PATH
with only a fake pip3 runs the install through it; a PATH with neither
exits 127 with the message; npm's error names the binary without a
fallback hint.
| return Ok(p); | ||
| } | ||
| if binary == "pip" { | ||
| if let Ok(p) = which::which("pip3") { |
There was a problem hiding this comment.
pip3 fallback is only applied to final execution, not to the token-enabled tree verifier. With a Corgea token present, run_parsed_install calls tree::resolve_tree before this exec closure; for pip that path still calls resolve_pip_tree(manager.binary_name(), ...), and src/precheck/tree.rs:49-51 does a direct which::which("pip"). On a system with only pip3, the tree pass returns NamedOnly (pip not found on PATH) and then this new fallback still runs the real install via pip3. For pip install -r requirements.txt, covers_input is true because of the requirements file, but parsed.targets can be empty, so the result is an install that proceeds with no requirement contents or transitives verified. Before this PR, the same host failed closed at exec because pip was missing.
Concrete fix: centralize package-manager binary resolution and use the same pip -> pip3 fallback in tree::resolve_pip_tree before running the dry-run. Add an e2e test with a token/verdict stub, a controlled PATH containing only fake pip3, and corgea pip install -r requirements.txt; assert the dry-run uses pip3 and the tree verdicts are enforced instead of degrading to named-only.


Unit 5 of the install-vuln-gate dogfood frictions.
What
exec_commandresolution now goes through aresolve_binaryhelper: whenwhich::which("pip")fails it retriespip3(pip is the one manager with a conventional alias). All exec paths inherit it — install, passthrough subcommands, and the empty-cmd case.error: 'pip' not found on PATH (also tried 'pip3')error: 'npm' not found on PATHshould_block_installuntouched.Tests
New hermetic e2e suite
tests/cli_exec_fallback.rs(fake-PM-on-controlled-PATH pattern fromcli_install.rs):pip3→corgea pip install oldpkg==1.0.0runs it with forwarded args (exit 0).corgea pip list).corgea npm listwith empty PATH → exit 127, error namesnpm, no fallback hint../harness checkgreen (clippy strict, fmt, 256 tests, deps skill).Known follow-up (out of unit scope)
src/precheck/tree.rs::resolve_pip_treedoes its ownwhich::which("pip")without the pip3 fallback, so with a token on a pip3-only system the tree pass degrades to named-only (with its loud warning) even though the install itself runs via pip3. Worth routing through the same resolver in a tree-pass unit.