Add hint for misplaced `--verbose` in `uv tool run` (#17020)

Resolves #16777

## Summary
When a command fails, users sometimes add --verbose after the package
name (e.g., uvx foo --verbose) instead of before it (e.g., uvx --verbose
foo). This adds a hint that suggests moving --verbose before the
command.
The hint appears when a verbose flag is detected in the subcommand
arguments and the command fails to resolve. It works for both uvx and uv
tool run.

## Test Plan
Tested by running:
uvx foo-does-not-exist --verbose - shows the hint
uv tool run foo-does-not-exist --verbose - shows the hint
The hint only appears when verbose flags are detected, and the message
shows the correct command format.

## Screenshot
<img width="920" height="34" alt="image"
src="https://github.com/user-attachments/assets/f6c303f6-b5e6-441f-8d8d-9f5e6ab87c87"
/>

Open to feedback and happy to make changes as needed! 💯

---------

Co-authored-by: Tomasz (Tom) Kramkowski <tom@astral.sh>
This commit is contained in:
F4RAN 2025-12-09 19:45:06 +03:30 committed by GitHub
parent a70ee58ae1
commit 38ce3b2919
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 101 additions and 2 deletions

View File

@ -80,6 +80,20 @@ impl Display for ToolRunCommand {
}
}
/// Check if the given arguments contain a verbose flag (e.g., `--verbose`, `-v`, `-vv`, etc.)
fn find_verbose_flag(args: &[std::ffi::OsString]) -> Option<&str> {
args.iter().find_map(|arg| {
let arg_str = arg.to_str()?;
if arg_str == "--verbose" {
Some("--verbose")
} else if arg_str.starts_with("-v") && arg_str.chars().skip(1).all(|c| c == 'v') {
Some(arg_str)
} else {
None
}
})
}
/// Run a command.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn run(
@ -309,11 +323,24 @@ pub(crate) async fn run(
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls())
.with_context("tool")
let diagnostic =
diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls());
let diagnostic = if let Some(verbose_flag) = find_verbose_flag(args) {
diagnostic.with_hint(format!(
"You provided `{}` to `{}`. Did you mean to provide it to `{}`? e.g., `{}`",
verbose_flag.cyan(),
target.cyan(),
invocation_source.to_string().cyan(),
format!("{invocation_source} {verbose_flag} {target}").green()
))
} else {
diagnostic.with_context("tool")
};
return diagnostic
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(ProjectError::Requirements(err)) => {
let err = miette::Report::msg(format!("{err}"))
.context("Failed to resolve `--with` requirement");

View File

@ -2820,6 +2820,78 @@ fn tool_run_with_script_and_from_script() {
");
}
/// Test that when a user provides `--verbose` to the subcommand,
/// we show a helpful hint.
#[test]
fn tool_run_verbose_hint() {
let context = TestContext::new("3.12").with_filtered_counts();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// Test with --verbose flag
uv_snapshot!(context.filters(), context.tool_run()
.arg("nonexistent-package-foo")
.arg("--verbose")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because nonexistent-package-foo was not found in the package registry and you require nonexistent-package-foo, we can conclude that your requirements are unsatisfiable.
help: You provided `--verbose` to `nonexistent-package-foo`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run --verbose nonexistent-package-foo`
"###);
// Test with -v flag
uv_snapshot!(context.filters(), context.tool_run()
.arg("nonexistent-package-bar")
.arg("-v")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because nonexistent-package-bar was not found in the package registry and you require nonexistent-package-bar, we can conclude that your requirements are unsatisfiable.
help: You provided `-v` to `nonexistent-package-bar`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run -v nonexistent-package-bar`
"###);
// Test with -vv flag
uv_snapshot!(context.filters(), context.tool_run()
.arg("nonexistent-package-baz")
.arg("-vv")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because nonexistent-package-baz was not found in the package registry and you require nonexistent-package-baz, we can conclude that your requirements are unsatisfiable.
help: You provided `-vv` to `nonexistent-package-baz`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run -vv nonexistent-package-baz`
"###);
// Test for false positives
uv_snapshot!(context.filters(), context.tool_run()
.arg("nonexistent-package-quux")
.arg("-version")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving tool dependencies:
Because nonexistent-package-quux was not found in the package registry and you require nonexistent-package-quux, we can conclude that your requirements are unsatisfiable.
");
}
#[test]
fn tool_run_with_compatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.9")