Skip to content

[Bug]: error_type on CallableRuntimeError is lost when using ctx.parallel() — double-wrapped by ErrorObject.from_exception() #297

@Iamrodos

Description

@Iamrodos

Expected Behavior

When a custom exception is raised inside a ctx.parallel() branch, I'd expect error_type on the resulting CallableRuntimeError (from batch_result.throw_if_error()) to be my original exception's class name — the same way it works with ctx.run_in_child_context(), where error_type correctly preserves the original class name.

try:
    result.throw_if_error()
except CallableRuntimeError as e:
    print(e.error_type)  # Expected: 'PermanentFailure'

Actual Behavior

error_type always comes back as 'CallableRuntimeError' instead of my original exception type. It looks like the exception goes through ErrorObject.from_exception() twice:

First wrap (in ChildOperationExecutor.execute(), operation/child.py):

  • My code raises PermanentFailure("Invalid input data")
  • ErrorObject.from_exception(e)ErrorObject(type="PermanentFailure") — correct
  • to_callable_runtime_error()CallableRuntimeError(error_type="PermanentFailure") — correct

Second wrap (in ConcurrentExecutor._create_result(), concurrency/executor.py):

  • ErrorObject.from_exception(executable.error) is called on the already-wrapped CallableRuntimeError
  • type(exception).__name__ is now CallableRuntimeError, so the original error_type="PermanentFailure" is lost

I'm wondering if I'm doing something wrong, or if this is a gap in how exceptions flow through parallel branches?

Steps to Reproduce

  1. Define a custom exception and a durable function that uses ctx.parallel():
class PermanentFailure(Exception):
    pass

def my_handler(ctx):
    def branch1(child_ctx):
        raise PermanentFailure("Invalid input data")

    result = ctx.parallel([branch1])
    result.throw_if_error()
  1. Catch the CallableRuntimeError and inspect error_type:
try:
    result.throw_if_error()
except CallableRuntimeError as e:
    print(e.error_type)  # Returns 'CallableRuntimeError', not 'PermanentFailure'
  1. Compare with ctx.run_in_child_context() which correctly preserves error_type='PermanentFailure' (only one wrap).

SDK Version

1.3.0

Python Version

3.14

Is this a regression?

No

Last Working Version

No response

Additional Context

Possible fix? Would it make sense for ErrorObject.from_exception() to check if the exception is already a CallableRuntimeError and preserve its error_type?

@classmethod
def from_exception(cls, exception: Exception) -> ErrorObject:
    if isinstance(exception, CallableRuntimeError) and exception.error_type:
        return cls(
            message=str(exception),
            type=exception.error_type,  # Preserve original type
            data=exception.data,
            stack_trace=exception.stack_trace,
        )
    return cls(
        message=str(exception),
        type=type(exception).__name__,
        data=None,
        stack_trace=None,
    )

Workaround: Using serial ctx.run_in_child_context() calls instead of ctx.parallel(), which only wraps once and preserves error_type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions