Files
Donald Pinckney 44eba4e91c Add .NET SDK support to temporal-developer skill (#39)
* Add .NET reference files for temporal-developer skill

Created 11 .NET reference files covering: dotnet.md (overview/quick start),
patterns.md, determinism.md, determinism-protection.md, error-handling.md,
testing.md, versioning.md, observability.md, data-handling.md, gotchas.md,
and advanced-features.md. Follows Python/TypeScript patterns with .NET-specific
content for Task determinism, CancellationToken, dependency injection, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix .NET alignment issues from self-review

- dotnet.md: Reduce Determinism Rules section to brief cross-reference
  (was duplicating determinism.md content)
- patterns.md: Add ParentClosePolicy to Child Workflows example
- gotchas.md: Add missing "Heartbeat Timeout Too Short" subsection
- versioning.md: Add missing Key Concepts, Deployment Strategies,
  Query Filters, PINNED/AUTO_UPGRADE guidance, CLI examples
- advanced-features.md: Add worker-level heading for exception types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix .NET correctness issues from verification pass

- patterns.md: Fix cancellation pattern to use official
  TemporalException.IsCanceledException(e) with detached CancellationTokenSource
- advanced-features.md: Fix DI hosting example to use official
  AddHostedTemporalWorker(clientTargetHost:, clientNamespace:, taskQueue:) pattern

Verified against official SDK README, API docs, and temporal-docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update supported language references to include .NET

- SKILL.md: Add "Temporal .NET" and "Temporal C#" trigger phrases,
  update overview to mention .NET, add .NET entry in getting started
- core/determinism.md: Add .NET entry in SDK Protection Mechanisms

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Edits to advanced features

* edits to determinism protection, and move the .editorconfig section

* missed one

* edit determinism.md

* edit error-handling.md

* edit gotchas.md

* edit patterns.md

* edit versioning.md

* edit observability.md

* fix metrics

* self-review round 1

* minor correctness fixed

* Update references/dotnet/patterns.md

Co-authored-by: Justin Anderson <44687433+jmaeagle99@users.noreply.github.com>

* address comments, clarify reference to earlier code snippet

* clarify that operations are forbidden IN WORKFLOWS

* cleanup workflow cancellation handling example

* add task token retrieval comment

* update .net requirements

* Fix propagation of workflow cancellation

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Justin Anderson <44687433+jmaeagle99@users.noreply.github.com>
2026-04-17 14:28:02 -04:00

4.1 KiB

Python SDK Error Handling

Overview

The Python SDK uses ApplicationError for application-specific errors and provides comprehensive retry policy configuration. Generally, the following information about errors and retryability applies across activities, child workflows and Nexus operations.

Application Errors

from temporalio import activity
from temporalio.exceptions import ApplicationError

@activity.defn
async def validate_order(order: Order) -> None:
    if not order.is_valid():
        raise ApplicationError(
            "Invalid order",
            type="ValidationError",
        )

Non-Retryable Errors

from dataclasses import dataclass
from temporalio import activity
from temporalio.exceptions import ApplicationError

@dataclass
class ChargeCardInput:
    card_number: str
    amount: float

@activity.defn
async def charge_card(input: ChargeCardInput) -> str:
    if not is_valid_card(input.card_number):
        raise ApplicationError(
            "Permanent failure - invalid credit card",
            type="PaymentError",
            non_retryable=True,  # Will not retry activity
        )
    return await process_payment(input.card_number, input.amount)

Handling Activity Errors

from datetime import timedelta
from temporalio import workflow
from temporalio.exceptions import ActivityError, ApplicationError, is_cancelled_exception

@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self) -> str:
        try:
            return await workflow.execute_activity(
                risky_activity,
                start_to_close_timeout=timedelta(minutes=5),
            )
        except ActivityError as e:
            # Let cancellation propagate so the workflow is canceled, not failed
            if is_cancelled_exception(e):
                raise
            workflow.logger.error(f"Activity failed: {e}")
            # Handle or re-raise
            raise ApplicationError("Workflow failed due to activity error")

Retry Policy Configuration

from datetime import timedelta
from temporalio import workflow
from temporalio.common import RetryPolicy

@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self) -> str:
        result = await workflow.execute_activity(
            my_activity,
            start_to_close_timeout=timedelta(minutes=10),
            retry_policy=RetryPolicy(
                maximum_interval=timedelta(minutes=1),
                maximum_attempts=5,
                non_retryable_error_types=["ValidationError", "PaymentError"],
            ),
        )
        return result

Only set options such as maximum_interval, maximum_attempts etc. if you have a domain-specific reason to. If not, prefer to leave them at their defaults.

Timeout Configuration

from datetime import timedelta
from temporalio import workflow

@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self) -> str:
        return await workflow.execute_activity(
            my_activity,
            start_to_close_timeout=timedelta(minutes=5),      # Single attempt
            schedule_to_close_timeout=timedelta(minutes=30),  # Including retries
            heartbeat_timeout=timedelta(minutes=2),          # Between heartbeats
        )

Workflow Failure

from temporalio import workflow
from temporalio.exceptions import ApplicationError

@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self) -> str:
        if some_condition:
            raise ApplicationError(
                "Cannot process order",
                type="BusinessError",
            )
        return "success"

Note: Do not use non_retryable= with ApplicationError inside a worklow (as opposed to an activity).

Best Practices

  1. Use specific error types for different failure modes
  2. Mark permanent failures as non-retryable
  3. Configure appropriate retry policies
  4. Log errors before re-raising
  5. Use ActivityError to catch activity failures in workflows
  6. Design code to be idempotent for safe retries (see more at references/core/patterns.md)