Tool error propagation differs on a per tool basis

Observed behaviour

When create_merge_request returns {"error": "Request failed (...): HTTP 403: ..."}, the OneOffComponent sets execution_result to "success" and the flow continues to the supervisor step with no MR created.

Root cause — string-parsing-based error detection

The ToolNodeWithErrorCorrection determines success or failure in two stages:

  1. _execute_tool() — wraps tool.ainvoke() in a try/except. If an exception escapes, it is caught, formatted into a specific string, and returned.

  2. _extract_errors_from_responses() — scans the returned strings for regex patterns that match the formats produced by step 1:

    error_patterns = [
        r"tool exception occurred due to",    # _format_tool_exception
        r"execution failed due to",           # _format_type_error_response
        r"raised validation error",           # _format_validation_error
        r"runtime exception due to",          # _format_execution_error
        r"tool \w+ not found",               # tool-not-found case
    ]

The problem is that many tools — including CreateMergeRequest — catch exceptions inside their own _execute() method and return a JSON error payload instead of letting the exception propagate:

# CreateMergeRequest._execute()
try:
    response = self._process_http_response(...)  # raises ToolException on 403
    return json.dumps({"created_merge_request": response})
except Exception as e:
    return json.dumps({"error": str(e)})  # ← returned as a "normal" string

Because the exception never reaches _execute_tool(), the tool node:

  • Logs WORKFLOW_TOOL_SUCCESS (incorrect)
  • Fires on_tool_execution_success UI event (incorrect)
  • Returns the JSON error string as a normal tool response

Then _extract_errors_from_responses() scans the string {"error": "Request failed ..."} against its regex patterns — none match — and concludes the execution was successful.

This is a systemic issue. The same pattern exists in many tools: CreateMergeRequest, CreateBranch, CreateMergeRequestNote, CiLinter, UpdateMergeRequest, and others that use _process_http_response inside a try/except in _execute().

Affected code

File What
duo_workflow_service/agent_platform/v1/components/one_off/nodes/tool_node_with_error_correction.py _execute_tool(), _extract_errors_from_responses()
duo_workflow_service/agent_platform/experimental/components/one_off/nodes/tool_node_with_error_correction.py Same (experimental copy)

Desired Outcome

Tools are audited on whether they transparently propagate errors rather than having their own handling, and fixed accordingly.

Edited by Sebastian Rehm