tool:run masks the failure message: getOutputValues() throws on unset outputs of a failed tool
## Problem `drush tool:run` shows nothing useful when a tool fails. Instead of the failure message, it throws a `ContextException` and exits. The command reads the result, then unconditionally reads the outputs: ```php $success = $tool->getResultStatus(); $message = (string) $tool->getResultMessage(); $outputs = $tool->getOutputValues(); ``` A tool that returns `ExecutableResult::failure()` never sets its declared output contexts. `getOutputValues()` loops over every declared output and calls `getOutputValue()`, which throws on an unset context: ```php throw new ContextException(sprintf("The provided context '%s' is not valid.", $name)); ``` So the command dies at `getOutputValues()` — before it ever prints `$message`. Any tool that declares an output and can fail hits this. The actual failure reason is lost. ## Steps to reproduce 1. Write (or use) a tool that declares an `output_definitions` entry and returns `ExecutableResult::failure('some helpful message')` on bad input. 2. Run it with input that triggers the failure: `drush tool:run my_tool --input='{...}'`. 3. Instead of `Failed: some helpful message`, you get: ``` In TypedOutputsTrait.php line 59: The provided context 'my_output' is not valid. ``` ## Cause `ToolRunCommand::runTool()` reads outputs regardless of whether the tool succeeded. The MCP bridge (`mcp_server_tool_bridge`) already does the right thing — it reads outputs only inside `if ($result->isSuccess())` — so the same failure surfaces its message correctly over MCP but not over the CLI. ## Proposed fix Read outputs only on success, mirroring the bridge: ```php $success = $tool->getResultStatus(); $message = (string) $tool->getResultMessage(); $outputs = $success ? $tool->getOutputValues() : []; ``` Patch attached below. ```diff --- a/src/Drush/Commands/ToolRunCommand.php +++ b/src/Drush/Commands/ToolRunCommand.php @@ -141,7 +141,11 @@ // Get result. $success = $tool->getResultStatus(); $message = (string) $tool->getResultMessage(); - $outputs = $tool->getOutputValues(); + // Outputs are only populated on success. A failed tool leaves its declared + // output contexts unset, and getOutputValues() throws a ContextException on + // those, which would mask the failure message. Mirror the MCP bridge, which + // reads outputs only when the result is successful. + $outputs = $success ? $tool->getOutputValues() : []; if ($options['json']) { $result = [ ```
issue