Add custom FailureDetails properties - #253
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
There was a problem hiding this comment.
Pull request overview
This PR extends the Durable Task Python SDK to support portable, custom exception metadata by introducing an ExceptionPropertiesProvider and plumbing its output into FailureDetails so properties (and inner failures) round-trip through task exceptions, orchestration state, history, and entity failures.
Changes:
- Added
ExceptionPropertiesProvider(worker option) and propagated it through worker execution paths to enrich protobufTaskFailureDetails. - Extended the public
FailureDetailsmodel (inner failures + properties) and updated parsing in client/history/task failure surfaces. - Added tests, README documentation, and changelog entries for core + Azure Managed packages.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/durabletask/test_failure_details.py | Adds unit tests validating property serialization, inner-failure preservation, and provider failure behavior. |
| tests/durabletask/entities/test_entity_failure_handling.py | Adds an E2E entity test ensuring exception properties are observable by orchestrators. |
| tests/durabletask-azuremanaged/test_azuremanaged_grpc_resiliency.py | Verifies Azure Managed worker forwards the provider to the base worker. |
| README.md | Documents feature usage, supported value shapes, and .NET dt:/dto: parity gap. |
| durabletask/worker.py | Introduces the provider Protocol and threads it through orchestration/activity/entity failure reporting. |
| durabletask/task.py | Extends FailureDetails and ensures TaskFailedError.details uses the shared protobuf-to-model conversion. |
| durabletask/internal/helpers.py | Implements protobuf<->Python value conversion plus failure-details property extraction. |
| durabletask/history.py | Switches history failure-details parsing to the shared protobuf converter (to include properties/inner failures). |
| durabletask/client.py | Switches orchestration-state failure-details parsing to the shared protobuf converter. |
| durabletask/init.py | Exposes ExceptionPropertiesProvider from the package root. |
| durabletask-azuremanaged/durabletask/azuremanaged/worker.py | Adds exception_properties_provider to DurableTaskSchedulerWorker and forwards it to the base worker. |
| durabletask-azuremanaged/CHANGELOG.md | Adds Unreleased entry documenting the new worker option. |
| CHANGELOG.md | Adds Unreleased entry documenting the new provider and failure-details enrichment. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
Bernd Verst (berndverst)
left a comment
There was a problem hiding this comment.
Requesting changes because an unhandled failed task drops its original custom properties and inner-failure chain when it fails the parent orchestration. Core history-export state metadata also omits the new fields.
| pb.ORCHESTRATION_STATUS_FAILED, | ||
| None, | ||
| ph.new_failure_details(ex) if isinstance(ex, Exception) else ex, | ||
| ph.new_failure_details( |
There was a problem hiding this comment.
Unhandled task failures lose the metadata this PR just captured. When an activity, entity, or sub-orchestration failure escapes the orchestrator, ex is a TaskFailedError; rebuilding it here serializes only that wrapper and calls the provider on TaskFailedError, while its existing details (the original properties and inner chain) are discarded. With a provider that returns {"code": "VALUE"} only for ValueError, the activity failure has the property, but the final OrchestrationState.failure_details has properties=None and inner_failure=None. Please carry the underlying task failure into the orchestration failure (for example as its inner failure) and add an unhandled-task regression test.
There was a problem hiding this comment.
Fixed in ddaa948. Unhandled TaskFailedError instances now retain the original protobuf TaskFailureDetails as the orchestration failure's inner failure, preserving custom properties and the complete existing inner-failure chain. Added a regression test.
| error_type: str | ||
| stack_trace: str | None | ||
| inner_failure: FailureDetails | None = None | ||
| properties: dict[str, Any] | None = None |
There was a problem hiding this comment.
One core serialization path still drops these fields: durabletask.extensions.history_export.serialization.orchestration_state_to_dict() manually emits only message/type/stack and one inner level, so exported state metadata omits properties and truncates deeper causes, while failure-bearing history events include them. Please use a shared recursive FailureDetails serializer there and cover state metadata with properties plus a multi-level inner chain.
There was a problem hiding this comment.
Fixed in ddaa948. History export now uses a recursive FailureDetails serializer for orchestration-state metadata, including properties and every inner-failure level. Added multi-level coverage.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 26daf565-a1f4-4bf0-9540-b57f31a4a12b
Summary
ExceptionPropertiesProviderand worker configuration for portable exception metadata.dt:/dto:.NET parity gap; add core and Azure Managed changelog entries.Validation
python -m pytest tests\durabletask\test_failure_details.py tests\durabletask\entities\test_entity_failure_handling.pypython -m pytest tests\durabletask-azuremanaged\test_azuremanaged_grpc_resiliency.pypython -m flake8 durabletaskpython -m flake8 tests\durabletaskpython -m flake8 durabletask-azuremanagedpython -m flake8 tests\durabletask-azuremanagedpython -m pyrighton changed production filespython -m pymarkdown -c .pymarkdown.json scan README.mdResolves #67