Skip to content

fix: preserve received failure properties - #375

Merged
wangbill (YunchuWang) merged 1 commit into
mainfrom
yunchuwang-preserve-failure-properties
Sep 28, 2026
Merged

wangbill (YunchuWang) merged 1 commit into
mainfrom
yunchuwang-preserve-failure-properties

Conversation

@YunchuWang

@YunchuWang wangbill (YunchuWang) commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

What changed?

  • Before: the SDK discarded structured TaskFailureDetails.properties already present on the wire. After: optional readonly properties survive task errors, retry callbacks, client state/waits/query/history, entity failure conversion, and in-memory/Functions testing.
  • Forward root and nested received properties beneath the existing TaskFailedError wrapper when uncaught or rethrown. Preserve constructor compatibility, messages, explicit cause precedence, and existing failure-chain cycle markers.

Why is this change needed?

  • Match the receiving/forwarding portion of the .NET failure contract: immutable .NET wire conversion and property conversion/forwarding.
  • Consumer-only scope: no arbitrary JavaScript Error property collection, exception provider, custom data converter, or retry-rule change.

Issues / work items


Project checklist

  • Release notes are not required for the next release
    • Otherwise: Notes added to CHANGELOG.md
  • Backport is not required
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change?
    • If yes:
      • Impact: N/A; optional bag and trailing constructor argument.
      • Migration guidance: N/A.

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot.
  • AI-assisted areas/files: all changes in this PR (failure projections, shared conversion, tests, README, changelogs).
  • What you changed after AI output: agent iterated from runtime RED to GREEN, completed retry projections, and corrected fixture typing/formatting. No human review or understanding is attested here.

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Human verification boxes are intentionally left for the reviewer; agent-run evidence follows.


Testing

Automated tests

  • Result: Passed. Runtime RED before production: 22 failures/33 passes across four focused suites, including actual missing received properties. Final: 192 tests, 12 suites passed, with open-handle detection.
  • Exact Jest command (repository root):
npm exec --no -- jest --runInBand --silent --detectOpenHandles --runTestsByPath packages\durabletask-js\test\failure-details.spec.ts packages\durabletask-js\test\failure-details-grpc.spec.ts packages\durabletask-js\test\retry-handler-task.spec.ts packages\durabletask-js\test\retry-handler.spec.ts packages\durabletask-js\test\retryable-task.spec.ts packages\durabletask-js\test\retry-policy-handle-failure.spec.ts packages\durabletask-js\test\history-event-converter.spec.ts packages\durabletask-js\test\new-orchestration-state.spec.ts packages\durabletask-js\test\entity-operation-failed-exception.spec.ts packages\durabletask-js\test\entity-operation-events.spec.ts packages\durabletask-js\test\pb-helper-versioning.spec.ts packages\azure-functions-durable\test\unit\testing.spec.ts
  • npm run build:core and npm run build -w durable-functions passed; the latter also rebuilds core.
  • ESLint passed on all 11 changed TypeScript files, including normal pre-commit hooks. git diff --check passed. Prettier passed for the eight changed files with clean formatting baselines (line endings normalized for comparison); existing formatting debt in six other files was left untouched rather than causing unrelated churn.

Manual validation (only if runtime/behavior changed)

  • Environment (OS, Node.js version, components): Windows, Node.js v24.14.0, local gRPC loopback and in-memory fixtures.
  • Steps + observed results:
    1. Inject legitimate protobuf property maps simulating a foreign worker; execute the real worker/executor; serialize completion and read through client get/waits/query/history. Root/nested values and falsy values survive uncaught and caught/rethrown failures and retry inspection.
    2. Exercise entity conversion, the Functions testing projection, existing cycle markers, unsupported property mutations, and legacy missing properties.
    3. Initial implementation evidence above was local. Follow-up on 2026-09-28 at unchanged 895fe72: four live scenarios passed on real production Azure DTS, covering uncaught/rethrown failures, custom-handler retry and policy/child propagation. A producer-only fixture adds valid properties before the activity completion is submitted; Azure persistence, dispatch and client reads are real and unmodified. Query uses fetchInputsAndOutputs:true. Exact-head evidence, initial fixture correction, cleanup and coverage limits. No actual .NET worker or live Functions host was used; entity/Functions adapter/cyclic-input paths remain local-only coverage.
  • Evidence (optional): detailed RED/GREEN/build/lint/hook logs retained in the implementation session artifacts.

Notes for reviewers

  • Empty/absent wire maps use JavaScript undefined (not .NET's empty dictionary). Wire strings, including dt:/dto: prefixes, remain strings; no CLR date reconstruction.
  • Reuses protobuf Value conversion, with narrow forwarding validation for unsupported/cyclic user mutations. The installed protobuf dependency drops __proto__ map entries before SDK conversion; this dependency limitation is documented, not patched globally or in generated code. Adding that key before forwarding is rejected rather than silently lost.
  • Production delta: 74 additions / 1 deletion across seven files. Remaining changes are focused tests and documentation. No dependency/protocol/generated-code changes.

Retain structured backend properties in public failure details, retry inspection, entity and testing projections, and forwarded task failure chains. Do not collect arbitrary JavaScript Error fields.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e0af01a5-0dfa-4e71-a660-c4186e65d7e0
Copilot AI lite review requested due to automatic review settings September 28, 2026 19:45
@YunchuWang

wangbill (YunchuWang) commented Sep 28, 2026 •

Copy link
Copy Markdown
Member Author

Real Azure DTS validation — PASS at 895fe72

Validated unchanged head 895fe7263d9ceeae3a1490d97e905412aad3480e, tree e8425175d7653b5ab56859a8b0dc556e44f6c1fb, on 2026-09-28 against a new isolated production Azure DTS Consumption task hub in West US 2, in Private Test Sub WANGBILL. Azure CLI/TLS Hello and absent-instance GetInstance authentication passed on the first attempt.

All four live scenarios passed. The Jest run passed 2/2 tests: one local dependency/revision alignment test and one sequential live matrix containing the four scenarios. The run exited normally with open-handle detection in 13.287 seconds.

Live scenario Expected and observed result
Uncaught activity failure One activity attempt; terminal Failed state retains the task wrapper and rich root/nested received properties.
Catch and rethrow One activity attempt; catch inspection/custom status sees the exact values, and terminal Failed state retains them after forwarding.
Custom retry handler Handler reads received properties; the second activity attempt succeeds and the orchestration completes. A property named retryable:false does not automatically override SDK retry policy.
Policy callback inside a child orchestration Callback reads properties and refuses retry; one activity attempt; child failure reaches the parent with the expected two task-wrapper levels and intact properties.

Client get, waits, payload-enabled query (fetchInputsAndOutputs:true) and history match the expected values. Root and inner maps preserve strings, fractional numbers, 0, false, "", null, arrays and nested objects. Identically named keys with different root/inner values remain distinct; dt:/dto: strings remain literal strings.

What is real, and what is the test fixture?

A real JS activity executes and throws. Because this PR intentionally does not collect arbitrary JavaScript Error fields, a test-only producer hook adds valid protobuf property maps to the failed activity completion before that request is sent to Azure. Azure then persists and redispatches the failure to the unchanged JS worker. No inbound work item, history event, backend response or client result is replaced or modified.

Five observations came from actual service work items; four additional records came from persisted client history. Each persisted TaskFailed protobuf digest equals the corresponding outbound producer failure digest (159c039158c3575d531748ed87195dd564d8520180b93aba8730e0b94a1d0f7c). This is a real Azure transport/persistence/dispatch test with an explicitly constructed producer payload, not an actual .NET-worker interoperability run, and not a mocked backend.

Fixture correction and cleanup

The first run stopped at its query assertion because the harness omitted fetchInputsAndOutputs, whose default is false. The service returned status without failure details. Setting that query option to true was the only fixture correction; exact property assertions and production source were unchanged. The initial run/report/harness remain archived rather than counted as a pass.

All five instances from the successful run plus the first-run root were individually purged (deletedInstanceCount=1 each) and read back absent. All workers, clients and the test process stopped; the overlay was archived and removed, leaving a clean checkout at the tested head. All task-owned Azure resources are now deleted: temporary data role, task hub, scheduler and resource group. Azure confirmed the group absent at 2026-09-28T20:40:19Z. No customer resources were touched.

Evidence index: pr375-azure-validation-summary.json, full report pr375-5d8f4b8f-b3cb-4a8d-93ba-fa4bea123179.json, execution log and exact archived overlay. Overlay SHA-256: 3b03938093f886d71d89de39eda6f57117e114296f4b8a4551d5390836b45576.

Entities, the Functions testing adapter, cyclic user mutations and reserved-key handling retain local-only coverage; no cloud claims are made for those paths. No production source/dependency changes, commits, pushes, labels or merge actions were required.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@YunchuWang
wangbill (YunchuWang) merged commit b330688 into main Sep 28, 2026
32 checks passed
@YunchuWang
wangbill (YunchuWang) deleted the yunchuwang-preserve-failure-properties branch September 28, 2026 22:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants