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
- 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()
- Catch the
CallableRuntimeError and inspect error_type:
try:
result.throw_if_error()
except CallableRuntimeError as e:
print(e.error_type) # Returns 'CallableRuntimeError', not 'PermanentFailure'
- 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.
Expected Behavior
When a custom exception is raised inside a
ctx.parallel()branch, I'd expecterror_typeon the resultingCallableRuntimeError(frombatch_result.throw_if_error()) to be my original exception's class name — the same way it works withctx.run_in_child_context(), whereerror_typecorrectly preserves the original class name.Actual Behavior
error_typealways comes back as'CallableRuntimeError'instead of my original exception type. It looks like the exception goes throughErrorObject.from_exception()twice:First wrap (in
ChildOperationExecutor.execute(),operation/child.py):PermanentFailure("Invalid input data")ErrorObject.from_exception(e)→ErrorObject(type="PermanentFailure")— correctto_callable_runtime_error()→CallableRuntimeError(error_type="PermanentFailure")— correctSecond wrap (in
ConcurrentExecutor._create_result(),concurrency/executor.py):ErrorObject.from_exception(executable.error)is called on the already-wrappedCallableRuntimeErrortype(exception).__name__is nowCallableRuntimeError, so the originalerror_type="PermanentFailure"is lostI'm wondering if I'm doing something wrong, or if this is a gap in how exceptions flow through parallel branches?
Steps to Reproduce
ctx.parallel():CallableRuntimeErrorand inspecterror_type:ctx.run_in_child_context()which correctly preserveserror_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 aCallableRuntimeErrorand preserve itserror_type?Workaround: Using serial
ctx.run_in_child_context()calls instead ofctx.parallel(), which only wraps once and preserveserror_type.