temporalio/temporal · warning
ErrCancellationAlreadyRequested
ErrCancellationAlreadyRequested
Error message
%w with request ID %s
What it means
Requesting cancellation of a Nexus operation fails when a cancellation was already requested. The error wraps ErrCancellationAlreadyRequested and includes the request ID of the existing cancellation request so callers can deduplicate or correlate their requests. It is returned when the operation's Cancellation component already exists.
Source
Thrown at chasm/lib/nexusoperation/operation.go:190
// operation has already started, schedules the cancellation request to be sent to the Nexus endpoint.
func (o *Operation) RequestCancel(
ctx chasm.MutableContext,
req *nexusoperationpb.CancellationState,
) error {
// A cancel retry can arrive after the operation closed, so dedupe before rejecting terminal states.
existingCancellation, hasCanceled := o.Cancellation.TryGet(ctx)
if hasCanceled &&
existingCancellation.GetRequestId() == req.GetRequestId() {
return nil
}
if !TransitionCanceled.Possible(o) {
return ErrOperationAlreadyCompleted
}
if hasCanceled {
existingReqID := existingCancellation.GetRequestId()
return fmt.Errorf("%w with request ID %s", ErrCancellationAlreadyRequested, existingReqID)
}
cancel := newCancellation(req)
o.Cancellation = chasm.NewComponentField(ctx, cancel)
// Once started, the handler returns a token that can be used in the cancellation request.
// Until then, no need to schedule the cancellation.
if o.Status == nexusoperationpb.OPERATION_STATUS_STARTED {
return TransitionCancellationScheduled.Apply(cancel, ctx, EventCancellationScheduled{
Destination: o.GetEndpoint(),
})
}
return nil
}
// onStarted applies the started transition or delegates to the store if one is present.
func (o *Operation) onStarted(ctx chasm.MutableContext, operationToken string, startTime *time.Time, links []*commonpb.Link) error {
if store, ok := o.Store.TryGet(ctx); ok {
return store.OnNexusOperationStarted(ctx, o, operationToken, startTime, links)View on GitHub (pinned to bde624efd1)
Solutions
- Treat this as expected on retries: check errors.Is(err, ErrCancellationAlreadyRequested) and treat cancellation as in-flight, not failed
- Use the request ID in the message to compare with your own request ID — if they match, the earlier cancel succeeded
- Do not retry the cancellation with a new request ID; a fresh request ID will not be honored for an existing cancellation
- If the operation actually completed despite this, check operation status first (TransitionCanceled.Possible) before cancelling
Example fix
// before
err := nexusOp.RequestCancel(ctx, req)
if err != nil { return err }
// after
err := nexusOp.RequestCancel(ctx, req)
if errors.Is(err, ErrCancellationAlreadyRequested) {
// cancellation already in flight; proceed waiting for result
return nil
}
if err != nil { return err } Defensive patterns
Strategy: try-catch
Validate before calling
// Before cancelling, check operation state where accessible:
if !TransitionCanceled.Possible(op) { /* already completed; skip cancel */ } Try / catch
// Go
err := nexusOp.RequestCancel(ctx, req)
switch {
case err == nil:
// cancellation newly requested
case errors.Is(err, ErrCancellationAlreadyRequested):
// idempotent path: cancellation already in flight — not a failure
default:
return err
} Prevention
- Reuse the same request ID when retrying cancel operations so duplicates are detectable
- Track cancellation state in your workflow to avoid duplicate cancel calls
- Check operation completion status before issuing a cancel
- Treat ErrCancellationAlreadyRequested as success in idempotent workflows
When it happens
Trigger: Calling chasm NexusOperation RequestCancel (via ClientWorkflow NexusOperationCancel or the CancelNexusOperation API) on an operation whose Cancellation field is already set, i.e. cancel was previously requested (possibly by a different workflow run or a retry).
Common situations: A workflow retried a cancellation call after an ambiguous failure (timeout with unknown outcome); two workflows or two calls try to cancel the same operation; an operator manually re-issues a cancel that automation already sent.
Related errors
- ErrInvalidOperationToken
- link type is empty
- link type contains invalid char (valid chars: alphanumeric,
- empty operation name
- empty operation token
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/a4ee6b37e6b5d09b.
Report an issue: GitHub.