Skip to content

Expose activity retry metadata via trigger bindings, OpenTelemetry, and status history#3455

Open
bachuv wants to merge 18 commits into
devfrom
vabachu/activity-retry-extension-2
Open

Expose activity retry metadata via trigger bindings, OpenTelemetry, and status history#3455
bachuv wants to merge 18 commits into
devfrom
vabachu/activity-retry-extension-2

Conversation

@bachuv

@bachuv bachuv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

What changed?

  • Activity trigger binding now exposes three new metadata keys on every
    activity invocation when the activity was scheduled with a RetryOptions
    policy: durabletask.attempt (int), durabletask.maxAttempts (int), and
    durabletask.isMaxAttempt (bool). Workers read these from their
    language-specific binding metadata API (e.g. context.triggerMetadata in
    the Node.js worker). Keys are absent entirely when no retry policy is in
    use.
  • Per-attempt OpenTelemetry attributes are stamped on the activity-trigger
    span (durabletask.attempt, durabletask.max_attempts,
    durabletask.is_max_attempt) for both in-proc and out-of-proc paths.
  • Per-instance retry aggregates (durabletask.retry_attempt_count,
    durabletask.retry_max_attempts_reached) are emitted once on the
    orchestration-completion span on the terminal turn. Aggregator
    short-circuits when no retry-tagged events exist.
  • DurableClient.getStatus(showHistory: true) now preserves
    TaskScheduledEvent.Tags when folding TaskScheduled events into
    aggregated TaskCompleted / TaskFailed history items, so client-side
    consumers walking the returned history can still see dt.retry.* tags.
    Only attaches when non-empty to keep the response shape unchanged for
    the common no-tags case.
  • New diagnostic warning (one-time per process) when retry-tagged Tags are
    present but malformed — narrows the mixed-version / backend-stripped-tags
    detection signal without spamming the no-retry-policy case.

Why is this change needed?

  • Activity functions today have no first-class way to know which retry
    attempt they're on, how many attempts remain, or whether the current
    attempt is the final one. User code typically reimplements this with
    ad-hoc counters in instance state or by parsing exception messages,
    neither of which is robust.
  • OpenTelemetry exporters miss per-attempt context entirely — a span for
    attempt 3 of 5 looks identical to a fresh invocation, making retry
    failures hard to triage in distributed traces.
  • Client-side status inspection (dashboards, custom tooling) can't
    surface retry history because the host's response post-processor folds
    TaskScheduled events away and drops their Tags, hiding the metadata
    even when the backend persists it correctly.

Issues / work items

  • Resolves #
  • Related #

Project checklist

  • Documentation changes are not required
    • Otherwise: Documentation PR is ready to merge and referenced in pending_docs.md
  • Release notes are not required for the next release
    • Otherwise: Notes added to release_notes.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)
  • No extra work is required to be leveraged by OutOfProc SDKs
    • Otherwise: Work tracked here: #issue_or_pr_in_each_sdk
  • No change to the version of the WebJobs.Extensions.DurableTask package
    • Otherwise: Major/minor updates are reflected in /src/Worker.Extensions.DurableTask/AssemblyInfo.cs
  • No EventIds were added to EventSource logs
  • This change should be added to the v2.x branch
    • Otherwise: This change applies exclusively to WebJobs.Extensions.DurableTask v3.x and will be retained only in the dev and main branches
  • Breaking change?
    • If yes:
      • Impact:
      • Migration guidance:

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 (Claude) in the Dobby Durable Functions agent mode
  • AI-assisted areas/files:
    • New: ActivityRetryMetadata.cs, RetryHistoryAggregator.cs,
      RetryMetadataConstants.cs
    • Modified: Bindings/ActivityTriggerAttributeBindingProvider.cs,
      ContextImplementations/DurableActivityContext.cs,
      ContextImplementations/DurableClient.cs,
      DurableTaskExtension.cs, Listener/TaskActivityShim.cs,
      Listener/TaskOrchestrationShim.cs, OutOfProcMiddleware.cs

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

Testing

Automated tests

  • Result: Passed

Copilot AI review requested due to automatic review settings June 12, 2026 23:12

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.

Pull request overview

This PR adds a cross-stack “activity retry visibility” contract by parsing retry-related TaskScheduledEvent.Tags and surfacing them consistently via activity trigger metadata, OpenTelemetry span attributes, and (for client status queries) preserved history tags.

Changes:

  • Introduces a shared retry metadata/tag contract (dt.retry.*, durabletask.*) and parsing/aggregation helpers.
  • Emits per-attempt retry metadata to out-of-proc activity trigger metadata and (partially) to OpenTelemetry spans; emits per-instance aggregates on orchestration completion/failure spans.
  • Preserves TaskScheduledEvent.Tags when folding scheduled events into aggregated history items returned by DurableClient.getStatus(showHistory: true).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/WebJobs.Extensions.DurableTask/RetryMetadataConstants.cs Defines the wire-contract keys for history tags, trigger metadata, and OTel attributes.
src/WebJobs.Extensions.DurableTask/ActivityRetryMetadata.cs Adds parsing logic for retry metadata from TaskScheduledEvent.Tags.
src/WebJobs.Extensions.DurableTask/RetryHistoryAggregator.cs Adds orchestration-terminal aggregation of retry metrics to OTel spans.
src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Adds out-of-proc activity span stamping + warning, and orchestration-terminal aggregation stamping.
src/WebJobs.Extensions.DurableTask/Listener/TaskOrchestrationShim.cs Adds in-proc orchestration-terminal aggregation stamping.
src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs Propagates retry metadata into the activity context (but currently doesn’t stamp OTel attempt attributes).
src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs Plumbs retry metadata into the in-proc activity shim.
src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs Adds RetryMetadata storage on the activity context for binding metadata emission.
src/WebJobs.Extensions.DurableTask/Bindings/ActivityTriggerAttributeBindingProvider.cs Exposes retry metadata as trigger binding data keys when available.
src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Preserves TaskScheduledEvent.Tags through history folding so clients can still read dt.retry.*.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/WebJobs.Extensions.DurableTask/Listener/TaskActivityShim.cs
Comment thread src/WebJobs.Extensions.DurableTask/RetryHistoryAggregator.cs
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/ActivityRetryMetadata.cs
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed

@nytian nytian 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.

Did an initial review. Thanks for the PR! Also can we add some tests?

Comment thread src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Outdated

// Emit per-instance retry aggregate on the orchestration span for the
// "failed for some other reason" path as well — same terminal status.
RetryHistoryAggregator.EmitToActivity(runtimeState.Events, Activity.Current);

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.

Just confirm: runtimeState.Events include all history events or just session's events?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All history events.

// Tags" or mixed-version scenarios.
if (scheduledEvent.Tags != null
&& scheduledEvent.Tags.ContainsKey(RetryMetadataConstants.HistoryTagAttempt)
&& retryMetadata == null

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.

what is interlock/exchange for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Interlocked.Exchange ensures the warning fires exactly once per process. Without it, the same warning logging thousands of times would crowd out other diagnostics in the log stream.

Comment thread src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/RetryHistoryAggregator.cs Outdated
Copilot AI review requested due to automatic review settings June 16, 2026 23:03

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Fixed
Copilot AI review requested due to automatic review settings June 16, 2026 23:54

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs Fixed
Copilot AI review requested due to automatic review settings June 17, 2026 00:10

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs Outdated

@nytian nytian 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.

LGTM. the two comments are non-blocking

Comment thread src/WebJobs.Extensions.DurableTask/RetryHistoryAggregator.cs Outdated
Copilot AI review requested due to automatic review settings June 22, 2026 20:35

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/RetryHistoryAggregator.cs Outdated
Copilot AI review requested due to automatic review settings June 22, 2026 21:16

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread src/WebJobs.Extensions.DurableTask/Listener/TaskOrchestrationShim.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableClient.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/ActivityRetryMetadata.cs Outdated
Comment thread src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs
}

anyTaggedFound = true;
if (meta.Value.Attempt > 1)

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.

If I'm understanding correctly, it seems that this counter actually counts the cumulative attempts across all retried Activities. Is this what we want? Do we even need this cumulative counter if we have the per-Activity retry counter (I remember seeing this in a previous file)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It would help answer which orchestration retried the most / hit their retry ceiling, but I'm open to discussing this further.

}
}

activity.SetTag(RetryMetadataConstants.SpanAttrRetryAttemptCount, retryAttemptCount);

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.

I am actually wondering if it even makes sense to add this information at the orchestration-level at all.. it just seems like we are repeating the information already included in the spans for (Durable) Activities

Comment thread test/FunctionsV2/Tests/Unit/DurableClientHistoryTagsTests.cs Outdated
Copilot AI review requested due to automatic review settings July 2, 2026 21:43

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment on lines +573 to +577
// Parse per-attempt retry metadata from the scheduling event's tags (if any) and attach
// it to the activity context so the binding layer can forward it to the worker via
// triggerMetadata.
ActivityRetryMetadata? retryMetadata = ActivityRetryMetadata.TryParseFromTags(scheduledEvent.Tags);
inputContext.RetryMetadata = retryMetadata;
Copilot AI review requested due to automatic review settings July 2, 2026 21:47

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.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

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.

4 participants