fix: sanitize unexpected confirmed Tool resolution errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Charles YaoandClaude Opus 5.5 committed 2026-10-11 16:47:54 -07:00
1 parent a7a0c5743b
commit 39da07d63a
2 files changed
+77 -2

No files matched your search

+10 -2
View File
@@ -604,9 +604,17 @@ class DifyToolNodeRuntime(ToolNodeRuntimeProtocol):
variable_pool,
confirmed_guard=guard,
)
except ToolNodeError:
raise
except BuilderExecutionPolicyError as exc:
# Declared refusals carry only their fixed reason code.
raise ToolRuntimeResolutionError(str(exc)) from exc
except Exception as exc:
if self._confirmed:
# Provider, cache or credential text must not leave the confirmed boundary.
raise ToolRuntimeResolutionError(
str(BuilderExecutionPolicyError("confirmed_tool_resolution_failed"))
) from None
if isinstance(exc, ToolNodeError):
raise
raise ToolRuntimeResolutionError(str(exc)) from exc
conversation_id = (
@@ -1638,6 +1638,73 @@ def test_configured_node_is_refused_before_the_factory_claims(monkeypatch: pytes
get_runtime.assert_not_called()
_RESOLUTION_SECRET = "resolution-secret-sentinel-8e2"
@pytest.mark.parametrize("use_factory", [False, True], ids=["ready-guard", "factory-guard"])
@pytest.mark.parametrize(
"error",
[
RuntimeError(f"daemon said {_RESOLUTION_SECRET}"),
ValueError(f"credential row {_RESOLUTION_SECRET}"),
node_runtime.ToolRuntimeResolutionError(f"native {_RESOLUTION_SECRET}"),
],
ids=["runtime-error", "value-error", "tool-node-error"],
)
def test_unexpected_confirmed_resolution_error_is_sanitized(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, error: Exception, use_factory: bool
) -> None:
get_runtime = MagicMock(side_effect=error)
monkeypatch.setattr(node_runtime.ToolManager, "get_workflow_tool_runtime", get_runtime)
if use_factory:
runtime = DifyToolNodeRuntime(_build_run_context(), confirmed_guard_factory=_GuardFactory([]))
else:
runtime = DifyToolNodeRuntime(
_build_run_context(), confirmed_guard=_confirmed_guard(node_execution_id="actual-exec-7")
)
with pytest.raises(node_runtime.ToolRuntimeResolutionError, match="confirmed_tool_resolution_failed") as raised:
runtime.get_runtime(
node_id="tool-node", node_data=_builtin_node_data(), variable_pool=None, node_execution_id="actual-exec-7"
)
assert _RESOLUTION_SECRET not in str(raised.value)
assert raised.value.__cause__ is None
assert raised.value.__suppress_context__ is True
assert _RESOLUTION_SECRET not in caplog.text
get_runtime.assert_called_once()
def test_declared_confirmed_policy_refusal_keeps_its_reason(monkeypatch: pytest.MonkeyPatch) -> None:
get_runtime = MagicMock(side_effect=BuilderExecutionPolicyError("credential_cache_unavailable"))
monkeypatch.setattr(node_runtime.ToolManager, "get_workflow_tool_runtime", get_runtime)
runtime = DifyToolNodeRuntime(_build_run_context(), confirmed_guard=_confirmed_guard())
with pytest.raises(node_runtime.ToolRuntimeResolutionError, match="credential_cache_unavailable") as raised:
runtime.get_runtime(
node_id="tool-node",
node_data=_builtin_node_data(),
variable_pool=None,
node_execution_id="node-execution-id",
)
assert "confirmed_tool_resolution_failed" not in str(raised.value)
def test_ordinary_resolution_error_keeps_native_text(monkeypatch: pytest.MonkeyPatch) -> None:
get_runtime = MagicMock(side_effect=RuntimeError("ordinary provider detail"))
monkeypatch.setattr(node_runtime.ToolManager, "get_workflow_tool_runtime", get_runtime)
runtime = DifyToolNodeRuntime(_build_run_context())
with pytest.raises(node_runtime.ToolRuntimeResolutionError, match="ordinary provider detail") as raised:
runtime.get_runtime(
node_id="tool-node",
node_data=_builtin_node_data(),
variable_pool=None,
node_execution_id="node-execution-id",
)
assert isinstance(raised.value.__cause__, RuntimeError)
def test_guard_and_factory_together_are_refused() -> None:
with pytest.raises(BuilderExecutionPolicyError, match="invalid_confirmed_configuration"):
DifyToolNodeRuntime(