* Add initial skill for testing, which is simply Steve's skill (#1) * Add initial skill for testing, which is simply Steve's skill * Rename skill to 'temporal-dev' and update version Updated skill name and version for Temporal Python. * Use claude to merge Steve's, Max's, and Mason's skills. (#2) * Use claude to merge Steve's, Max's, and Mason's skills. Did a review pass using claude's skill devlopment skills * Add missing things from Steve * trigger tweaks * Add in common gotchas from Johann * add simple feedback mechanism (#3) * Change skill name to kebab-case, for compatibility with Amp and Cline (#7) * Clean up references/core/ai-integration.md * Clean up references/core/common-gotchas.md * Clean up references/core/common-gotchas.md * Clean up references/core/determinism.md * Clean up references/core/determinism.md * Update error-reference.md * Update interactive-workflows.md * Clean up patterns.md * Cut shell scripts * Edit troubleshooting.md * remove interceptors for now * remove dynamic workflows * clarify on heartbeating of async activity completions, and prompt it a bit in relation to signals * Improve references/python/advanced-features.md * Use explicit namespace in connect * remove duplicated content from determinism.md, clean up * Improve references/python/data-handling.md * Prefer start_to_close_timeout * don't explicitely provide defaults for retry policies * error-handling.md cleanup * move idempotency patterns to patterns.md * remove multi-param activities * small edits * Unify sandbox stuff into one file * local activities aren't experimental * Clean up references/python/sync-vs-async.md * Cleanup observability.md, remove duplicated search attributes * Cut otel for now * cut a lot of duplicate stuff from python gotchas, address comments * de-duplicate content * Lots of improvements to testing * cleanup to top level of skill (like CLI install instructions), and to top-level of python * Improve patterns.md * clean up ai-patterns.md * Update readme with installation instructions * remove ts directory * De-couple core from python and TypeScript as much as possible * Remove TypeScript hints * add prompting for feedback at startup - wait for ethan on slack channel * shorten url * Update slack channel * Automated pass over on python cleanup & deduplication * Remove multi-patching from Python, since its obvious, dont waste tokens on it. (#34) * Add TypeScript (#31) Adds initial support for TypeScript to the skill --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * Fix typos and reference links (#36) * Fix typos and reference links * 2 more typo fixes * quick edit to readme (#37) * Fix saga compensations to run under cancellation protection (#43) When a workflow is cancelled mid-saga, compensations must run in a cancellation-protected scope, otherwise they are immediately cancelled before they can execute. - Python: wrap compensation loop in asyncio.shield() so it runs even when the workflow receives a CancelledError - TypeScript: wrap compensation loop in CancellationScope.nonCancellable() so it runs even when the root scope is cancelled (per official docs: "Cleanup logic must be in a nonCancellable scope") - TypeScript: also fix compensation registration order — register BEFORE calling the activity (was already correct in Python) Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Update readme for public preview (#45) * a few more readme tweaks (#46) * Add MIT License to the project (#47) * Add Go (supersedes other PR) (#38) * progress on go * Go translation workflow completed. * missed a few spots * Manual edits * Address feedback * Add gotcha about anonymous local activities * Sample code for payload converter * clarify sdk protection mechanisms * Setup CODEOWNERS to AI SDK team (#48) * Align version number in SKILL.md and plugin.json. (#49) --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
5.6 KiB
Go SDK Error Handling
Overview
The Go SDK uses error return values (not exceptions). All Temporal errors implement the error interface. Activity errors returned to workflows are wrapped in *temporal.ActivityError; use errors.As to unwrap them.
Application Errors
import "go.temporal.io/sdk/temporal"
func ValidateOrder(ctx context.Context, order Order) error {
if !order.IsValid() {
return temporal.NewApplicationError(
"Invalid order",
"ValidationError",
)
}
return nil
}
temporal.NewApplicationError(message, errType, details...) creates a retryable *temporal.ApplicationError. Use NewApplicationErrorWithCause to include a wrapped cause.
Non-Retryable Errors
func ChargeCard(ctx context.Context, input ChargeCardInput) (string, error) {
if !isValidCard(input.CardNumber) {
return "", temporal.NewNonRetryableApplicationError(
"Permanent failure - invalid credit card",
"PaymentError",
nil, // cause
)
}
return processPayment(input.CardNumber, input.Amount)
}
temporal.NewNonRetryableApplicationError(message, errType, cause, details...) is always non-retryable regardless of RetryPolicy. You can also mark error types as non-retryable in the RetryPolicy instead:
RetryPolicy: &temporal.RetryPolicy{
NonRetryableErrorTypes: []string{"PaymentError", "ValidationError"},
},
Handling Activity Errors in Workflows
import (
"errors"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/workflow"
)
func MyWorkflow(ctx workflow.Context) (string, error) {
var result string
err := workflow.ExecuteActivity(ctx, RiskyActivity).Get(ctx, &result)
if err != nil {
var applicationErr *temporal.ApplicationError
if errors.As(err, &applicationErr) {
switch applicationErr.Type() {
case "ValidationError":
// handle validation error
case "PaymentError":
// handle payment error
default:
// handle unknown error type
}
}
var timeoutErr *temporal.TimeoutError
if errors.As(err, &timeoutErr) {
switch timeoutErr.TimeoutType() {
case enumspb.TIMEOUT_TYPE_START_TO_CLOSE:
// handle start-to-close timeout
case enumspb.TIMEOUT_TYPE_HEARTBEAT:
// handle heartbeat timeout
}
}
var canceledErr *temporal.CanceledError
if errors.As(err, &canceledErr) {
// handle cancellation
}
var panicErr *temporal.PanicError
if errors.As(err, &panicErr) {
// panicErr.Error() and panicErr.StackTrace()
}
return "", err
}
return result, nil
}
Retry Configuration
import (
"time"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/workflow"
)
func MyWorkflow(ctx workflow.Context) error {
ao := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Minute,
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2.0,
MaximumInterval: time.Minute,
MaximumAttempts: 5,
NonRetryableErrorTypes: []string{"ValidationError", "PaymentError"},
},
}
ctx = workflow.WithActivityOptions(ctx, ao)
return workflow.ExecuteActivity(ctx, MyActivity).Get(ctx, nil)
}
Only set options such as MaximumInterval, MaximumAttempts, etc. if you have a domain-specific reason to. If not, prefer to leave them at their defaults.
Timeout Configuration
ao := workflow.ActivityOptions{
StartToCloseTimeout: 5 * time.Minute, // Single attempt max duration
ScheduleToCloseTimeout: 30 * time.Minute, // Total time including retries
ScheduleToStartTimeout: 10 * time.Minute, // Time waiting in task queue
HeartbeatTimeout: 2 * time.Minute, // Between heartbeats
}
ctx = workflow.WithActivityOptions(ctx, ao)
- StartToCloseTimeout: Max time for a single Activity Task Execution. Prefer this over ScheduleToCloseTimeout.
- ScheduleToCloseTimeout: Total time including retries.
- ScheduleToStartTimeout: Time an Activity Task can wait in the Task Queue before a Worker picks it up. Rarely needed.
- HeartbeatTimeout: Max time between heartbeats. Required for long-running activities to detect failures.
Either StartToCloseTimeout or ScheduleToCloseTimeout must be set.
Workflow Failure
Returning any error from a workflow function fails the execution. Return nil for success.
Important Go-specific behavior: In the Go SDK, returning any error from a workflow fails the workflow execution by default — there is no automatic retry. This differs from other SDKs (Python, TypeScript) where non-ApplicationError exceptions cause the workflow task to retry indefinitely. In Go, if you want workflow-level retries, you must explicitly set a RetryPolicy on the StartWorkflowOptions.
func MyWorkflow(ctx workflow.Context) (string, error) {
if someCondition {
return "", temporal.NewApplicationError(
"Cannot process order",
"BusinessError",
)
}
return "success", nil
}
To prevent workflow retry, return a non-retryable error:
return "", temporal.NewNonRetryableApplicationError(
"Unrecoverable failure",
"FatalError",
nil,
)
Note: If an activity returns a non-retryable error, the workflow receives an *temporal.ActivityError wrapping it. To fail the workflow without retry, wrap it in a new NewNonRetryableApplicationError.
Best Practices
- Use specific error types for different failure modes
- Mark permanent failures as non-retryable
- Set appropriate timeouts; prefer
StartToCloseTimeoutoverScheduleToCloseTimeout - Let Temporal handle retries via RetryPolicy rather than implementing retry logic yourself
- Use
errors.Asto unwrap and inspect specific error types - Design activities to be idempotent for safe retries (see
references/core/patterns.md)