Run external command with error handling. Args: command: Command and arguments as list tool_name: Name of the tool for logging timeout: Command timeout in seconds Returns: Tuple of (stdout, stderr, returncode)
(
command: list[str],
tool_name: str = "external_tool",
timeout: int = 30,
)
| 58 | def run_command( |
| 59 | command: list[str], |
| 60 | tool_name: str = "external_tool", |
| 61 | timeout: int = 30, |
| 62 | ) -> tuple[str, str, int]: |
| 63 | """Run external command with error handling. |
| 64 | |
| 65 | Args: |
| 66 | command: Command and arguments as list |
| 67 | tool_name: Name of the tool for logging |
| 68 | timeout: Command timeout in seconds |
| 69 | |
| 70 | Returns: |
| 71 | Tuple of (stdout, stderr, returncode) |
| 72 | |
| 73 | Raises: |
| 74 | ToolNotFoundError: If tool not found |
| 75 | ToolExecutionError: If execution fails |
| 76 | """ |
| 77 | try: |
| 78 | result = subprocess.run( |
| 79 | command, |
| 80 | capture_output=True, |
| 81 | text=True, |
| 82 | timeout=timeout, |
| 83 | ) |
| 84 | |
| 85 | if result.returncode != 0: |
| 86 | logger.warning( |
| 87 | f"{tool_name} returned non-zero exit code: {result.returncode}" |
| 88 | ) |
| 89 | if result.stderr: |
| 90 | logger.debug(f"{tool_name} stderr: {result.stderr}") |
| 91 | |
| 92 | return result.stdout, result.stderr, result.returncode |
| 93 | |
| 94 | except FileNotFoundError as e: |
| 95 | logger.error(f"Tool '{tool_name}' not found in PATH") |
| 96 | raise ToolNotFoundError(tool_name) from e |
| 97 | |
| 98 | except subprocess.TimeoutExpired as e: |
| 99 | logger.error(f"{tool_name} execution timeout after {timeout}s") |
| 100 | raise ToolExecutionError(tool_name, f"Timeout after {timeout}s") from e |
| 101 | |
| 102 | except Exception as e: |
| 103 | logger.error(f"{tool_name} execution failed: {e}") |
| 104 | raise ToolExecutionError(tool_name, str(e)) from e |
| 105 | |
| 106 | |
| 107 | def handle_tool_error( |
| 108 | tool_name: str, |
| 109 | fallback_value=None, |