temporalio/temporal · error

ErrInvalidOperationToken

ErrInvalidOperationToken

Error message

%w: length exceeds allowed limit (%d/%d)

What it means

When a Nexus operation handler returns a start result containing a pending-operations token, the token length must not exceed the configured MaxOperationTokenLength for the namespace. If it does, the result is rejected with ErrInvalidOperationToken wrapped with the actual and allowed lengths. This protects history from persisting oversized tokens.

Source

Thrown at chasm/lib/nexusoperation/operation_tasks.go:250

	// This happens when we accept the ScheduleNexusOperation command when the endpoint is not found in the
	// registry as indicated by the EndpointNotFoundAlwaysNonRetryable dynamic config.
	// The config has been removed but we keep this check for backward compatibility.
	if args.endpointID == "" {
		return nil, nexus.NewHandlerErrorf(nexus.HandlerErrorTypeNotFound, "endpoint not registered")
	}
	return lookupEndpoint(ctx, h.endpointRegistry, ns.ID(), args.endpointID, args.endpointName)
}

func (h *operationInvocationTaskHandler) validateStartResult(
	ns *namespace.Namespace,
	result *nexusrpc.ClientStartOperationResponse[*commonpb.Payload],
) error {
	if result == nil {
		return nil
	}
	tokenLimit := h.config.MaxOperationTokenLength(ns.Name().String())
	if result.Pending != nil && len(result.Pending.Token) > tokenLimit {
		return fmt.Errorf("%w: length exceeds allowed limit (%d/%d)", ErrInvalidOperationToken, len(result.Pending.Token), tokenLimit)
	}
	if result.Successful != nil && result.Successful.Size() > h.config.PayloadSizeLimit(ns.Name().String()) {
		return ErrResponseBodyTooLarge
	}
	return nil
}

type operationBackoffTaskHandler struct {
	chasm.PureTaskHandlerBase
	config *Config

	metricsHandler metrics.Handler
	logger         log.Logger
}

func newOperationBackoffTaskHandler(opts operationTaskHandlerOptions) *operationBackoffTaskHandler {
	return &operationBackoffTaskHandler{
		config:         opts.Config,

View on GitHub (pinned to bde624efd1)

Solutions

  1. Reduce the token size returned by the handler: keep it a compact opaque reference and store state externally or in the operation component
  2. Raise nexusoperation.pending token limit config (MaxOperationTokenLength) for the namespace if the current token size is legitimate
  3. Log/compute len(token) in the handler and compare against the configured limit before returning
  4. Check for accidental inclusion of large payloads (base64 blobs, full request bodies) in the token

Example fix

// before
return &nexus.OperationStartResult{Pending: &nexus.OperationPending{Token: bigToken}}, nil
// after
if len(token) > maxTokenLen { token = compactToken(token) }
return &nexus.OperationStartResult{Pending: &nexus.OperationPending{Token: token}}, nil
Defensive patterns

Strategy: validation

Validate before calling

tokenLimit := h.config.MaxOperationTokenLength(ns)
if len(result.Pending.Token) > tokenLimit {
    return fmt.Errorf("token too long: %d/%d", len(result.Pending.Token), tokenLimit)
}
if result.Successful.Size() > h.config.PayloadSizeLimit(ns) {
    return errors.New("response body too large")
}

Try / catch

// Go
err := operation.Execute(ctx, req)
if errors.Is(err, ErrInvalidOperationToken) {
    // shrink token and retry the handler result, or surface config mismatch
}

Prevention

When it happens

Trigger: A Nexus operation handler returns nexus.OperationStartResult with result.Pending.Token longer than MaxOperationTokenLength (namespace-configured limit) — validateStartResult is called from Execute after the handler returns.

Common situations: A handler encodes too much state into the pending token (e.g. embedding large payloads/IDs); server operator lowers MaxOperationTokenLength below existing handler token sizes; namespace-specific config divergence between dev and prod.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/64ca254f0a6041e4. Report an issue: GitHub.