Repository navigation
Description
Expected Behavior
For a failed callback whose serialized error contains only ErrorMessage or only ErrorType, the callback operation's OTel exception event should preserve that field and omit the missing field. This applies to both Execution and Invocation views, including completion after suspension/resume.
The callback operation must remain ERROR. When the callback failure escapes the handler, Workflow must remain ERROR and execution must remain FAILED. A handler that catches the failure and returns successfully must retain successful Workflow/execution outcomes.
Actual Behavior
The <name> create callback id span contains fields that were not supplied:
| Callback error | Actual exception.type |
Actual exception.message |
Expected change |
|---|---|---|---|
{"ErrorMessage":"Intentional callback failure"} |
Exception |
Intentional callback failure |
Omit exception.type |
{"ErrorType":"RejectedError"} |
RejectedError |
empty string | Omit exception.message |
Both cases also generate a synthetic exception.stacktrace from the carrier (Exception: Intentional callback failure\n or Exception\n); that adjacent problem is tracked in #886.
Steps to Reproduce
-
Configure either
ExecutionOtelPluginFactoryorInvocationOtelPluginFactorywith a real OTelTracerProvider,SimpleSpanProcessor, andInMemorySpanExporter. -
Wrap a handler with
durable_execution(handler, plugins=[factory]). The handler calls:def submitter(callback_id, context): pass def handler(event, context): return context.wait_for_callback(submitter, name="partial-error")
-
Run with
DurableFunctionTestRunner, wait for the callback ID and for the first invocation to finish withPENDING, then complete it using:runner.send_callback_failure( callback_id, error=ErrorObject.from_dict({"ErrorMessage": "Intentional callback failure"}), )
-
Wait for the resumed execution to fail. Inspect the
exceptionevent on the terminalpartial-error create callback idspan. -
Repeat with
{"ErrorType": "RejectedError"}and with the other OTel view.
The shared recorder alone also reproduces the field injection with the real OTel SDK:
from aws_durable_execution_sdk_python.lambda_service import ErrorObject
from aws_durable_execution_sdk_python_otel.exception_events import (
record_error_object_exception,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
tracer = provider.get_tracer("partial-error-repro")
for raw in (
{"ErrorMessage": "Intentional callback failure"},
{"ErrorType": "RejectedError"},
):
with tracer.start_as_current_span("callback") as span:
record_error_object_exception(span, ErrorObject.from_dict(raw))
for span in exporter.get_finished_spans():
print(dict(span.events[0].attributes))
provider.shutdown()SDK Version
Local source reproduction on main at 4625d01bbfa97da67050014851af7eb41c61a842, with OpenTelemetry SDK 1.45.2. This identifies the tested source revision, not a released-package version.
Python Version
Python 3.13.18; Hatch 1.18.2.
Is this a regression?
No known working version. The missing-type fallback existed before #812. That PR fixed preservation of supplied types but retained fallback behavior for missing metadata.
Additional Context
Cause and ownership: the core ErrorObject correctly retains the missing field as None. In exception_events.py, the OTel plugin constructs a plain Exception carrier and calls span.record_exception() with only the supplied attributes. OTel first derives type, message, and stacktrace from the carrier, then overlays those attributes. Omitting an attribute therefore does not remove OTel's generated value. Both views share this helper.
A simpler recording approach is to build the exception event directly from supplied serialized fields, for example with span.add_event("exception", attributes=...), without constructing an application or carrier exception. Keep timestamp handling coordinated with #865 and stacktrace handling with #886. Do not change core checkpoint serialization, callback exception classes, or the legitimate CallbackExternalError diagnostics on enclosing SDK scopes.
Validation performed: the public handler/local runner reproduction used real OTel spans and forced PENDING before callback completion. Both partial-error cases in both views retained ERROR callback/Workflow spans and FAILED execution status; the submitter ran once across resume. All four assertions requiring the missing type/message field to be absent failed. The existing test_status_mapping.py suite passed all 108 tests; its test_failed_operation_without_type_falls_back explicitly expects Exception, so the correcting PR must identify and explain that expectation change. No deployed Lambda/X-Ray trace was independently verified for this issue.
Acceptance criteria:
- Message-only callback failures omit
exception.type; type-only failures omitexception.message, in both views. - Supplied values, including explicit empty strings, remain distinct from absent fields; full type/message errors retain their original values.
- Cover completion observed in the current invocation and completion after suspension/resume, preserving stored failure behavior and ensuring completed submitter work is not repeated.
- Keep callback/Workflow/execution failure status and legitimate enclosing SDK exception diagnostics unchanged.
- Cover the shared operation and failed-attempt recorder paths without importing or reconstructing application exception classes.
- Coordinate shared conformance requirements separately. The inspected conformance model does not represent exception events, so existing conformance passes do not establish this field-level behavior.
Related work and boundaries:
- [Bug]: OTel operation and attempt events lose the original exception.type #804 / fix: OTel operation and attempt events lose the original exception.type #812 preserve a supplied exception type; they allow a fallback when type metadata is missing.
- [Bug]: Align OTel exception stacktrace emission with OpenTelemetry JS #886 addresses synthetic stacktrace and explicitly retains existing type/message fallback behavior. Coordinate the shared helper, but this issue owns the partial type/message behavior.
- [OTel]: Report terminal failures and keep exception events within span bounds #853 / fix: correct terminal telemetry status and timing #865 address failure status and event timing when diagnostics are absent.
- [Testing]: Preserve empty callback error history without changing runtime errors #862 / fix: preserve empty errors in state and history #870 address empty error state/history and diagnostic normalization. Entirely absent errors and empty error objects remain with that work; this issue focuses on present errors containing one of the two fields.
Activity
zhongkechen commentedon Nov 2, 2026
Severity - low
When it triggers. A failed callback supplies only
ErrorMessageor onlyErrorType. Both OTel views record that partial error through a plainExceptioncarrier, including after suspension and resume.Impact. A message-only error gains the invented type
Exception; a type-only error gains an empty message. Consumers cannot distinguish absent metadata from these generated values and may group message-only errors under a type that was never supplied. The actual supplied field remains intact. The issue's public-handler/local-runner checks retained callback and WorkflowERROR, executionFAILED, and one submitter execution across resume. Synthetic stacktraces are the adjacent issue #886.Why this severity. This is a field-presence fidelity defect on partial callback diagnostics, rather than lost failures or lost operation spans. The known diagnostic and correct failed outcome remain available, and core
ErrorObjectserialization already preserves the absent field. It deserves correction so telemetry does not invent metadata, but the demonstrated impact is lower than completion or replay-contract failures.Source reviewed: shared recorder, together with the issue's reported local exporter and resume results. No additional deployed Lambda/X-Ray validation was performed.