fix: preserve subprocess error type in tool runner

- raise a CalledProcessError subclass that surfaces stderr/stdout detail
- keep existing callers that catch subprocess.CalledProcessError working
- reject lavfi movie paths containing single quotes fail-closed
- add regression coverage for both review findings

Verification:
- python3 -m unittest tests.tools.test_base_tool_dependencies tests.tools.test_scene_detect_lavfi_escape tests.tools.test_cost_tracker_governance
- python3 -m py_compile tools/base_tool.py tools/analysis/scene_detect.py tools/cost_tracker.py tests/tools/test_base_tool_dependencies.py tests/tools/test_scene_detect_lavfi_escape.py tests/tools/test_cost_tracker_governance.py
This commit is contained in:
Morpheus
2026-07-03 22:00:38 +08:00
parent 6580987931
commit 14ebc56123
4 changed files with 43 additions and 8 deletions

View File

@@ -349,11 +349,37 @@ class BaseTool(ABC):
stderr = (exc.stderr or "").strip()
stdout = (exc.stdout or "").strip()
detail = stderr or stdout or str(exc)
raise RuntimeError(
f"Command failed with exit code {exc.returncode}: {' '.join(resolved_cmd)}\n{detail}"
raise ToolCommandError(
exc.returncode,
exc.cmd,
output=exc.output,
stderr=exc.stderr,
detail=detail,
) from exc
class ToolCommandError(subprocess.CalledProcessError):
"""CalledProcessError with stderr/stdout surfaced in str(error)."""
def __init__(
self,
returncode: int,
cmd: list[str],
*,
output: Optional[str] = None,
stderr: Optional[str] = None,
detail: str = "",
) -> None:
super().__init__(returncode, cmd, output=output, stderr=stderr)
self.detail = detail
def __str__(self) -> str:
base = super().__str__()
if self.detail:
return f"{base}\n{self.detail}"
return base
class DependencyError(Exception):
"""Raised when a tool's dependency is not satisfied."""
pass