Expected Behavior
Callback errors surfaced by DurableContext.wait_for_callback should retain the callback ID generated for the operation, consistent with the CallbackError.callback_id field and the callback error hierarchy introduced by #563. The value should be available both when the error first surfaces and on replay.
Actual Behavior
CallbackExternalError and CallbackTimeoutError are reconstructed at the failed child-context boundary with callback_id=None. The original nested __cause__ is also lost. CallbackSubmitterError is constructed without a callback ID as well.
The direct Callback.result() error is initially created with the ID, but ChildOperationExecutor serializes it through ErrorObject. DurableOperationError.from_error_fields() then reconstructs the registered callback type using only message, error_type, data, and stack_trace; callback_id defaults to None. wait_for_callback subsequently re-raises that reconstructed exception.
Steps to Reproduce
- Define a durable handler that calls
context.wait_for_callback(...) and catches CallbackError.
- Start the execution and obtain the callback ID from the callback operation.
- Complete the callback with
SendDurableExecutionCallbackFailure or the local runner equivalent.
- Read
error.callback_id in the handler.
Expected: the ID sent to the external caller.
Actual: None.
A local durable-runner regression test against PR #563 reproduced:
CallbackExternalError.callback_id == None
CallbackExternalError.__cause__ == None
SDK Version
Unreleased PR #563 (2ac05c8d712bcdac492eee07ddc930f7f9ef2088)
Python Version
3.14
Is this a regression?
No. Before #563, wait_for_callback surfaced a ChildContextError and the callback ID was already unavailable. This issue tracks completing the new callback-error metadata contract rather than restoring previously working behavior.
Additional Context
Relevant paths:
ErrorObject.raise_as_operation_error() reconstructs the inner exception.
DurableOperationError.from_error_fields() does not pass callback_id to callback subclasses.
DurableContext.wait_for_callback() re-raises the reconstructed callback error and constructs CallbackSubmitterError without an ID.
Add a durable-runner test that catches each callback error type across the actual child-context checkpoint/replay boundary; the existing mocked pass-through test does not exercise serialization.
Expected Behavior
Callback errors surfaced by
DurableContext.wait_for_callbackshould retain the callback ID generated for the operation, consistent with theCallbackError.callback_idfield and the callback error hierarchy introduced by #563. The value should be available both when the error first surfaces and on replay.Actual Behavior
CallbackExternalErrorandCallbackTimeoutErrorare reconstructed at the failed child-context boundary withcallback_id=None. The original nested__cause__is also lost.CallbackSubmitterErroris constructed without a callback ID as well.The direct
Callback.result()error is initially created with the ID, butChildOperationExecutorserializes it throughErrorObject.DurableOperationError.from_error_fields()then reconstructs the registered callback type using onlymessage,error_type,data, andstack_trace;callback_iddefaults toNone.wait_for_callbacksubsequently re-raises that reconstructed exception.Steps to Reproduce
context.wait_for_callback(...)and catchesCallbackError.SendDurableExecutionCallbackFailureor the local runner equivalent.error.callback_idin the handler.Expected: the ID sent to the external caller.
Actual:
None.A local durable-runner regression test against PR #563 reproduced:
SDK Version
Unreleased PR #563 (
2ac05c8d712bcdac492eee07ddc930f7f9ef2088)Python Version
3.14
Is this a regression?
No. Before #563,
wait_for_callbacksurfaced aChildContextErrorand the callback ID was already unavailable. This issue tracks completing the new callback-error metadata contract rather than restoring previously working behavior.Additional Context
Relevant paths:
ErrorObject.raise_as_operation_error()reconstructs the inner exception.DurableOperationError.from_error_fields()does not passcallback_idto callback subclasses.DurableContext.wait_for_callback()re-raises the reconstructed callback error and constructsCallbackSubmitterErrorwithout an ID.Add a durable-runner test that catches each callback error type across the actual child-context checkpoint/replay boundary; the existing mocked pass-through test does not exercise serialization.