temporalio/temporal · error
empty operation token
Error message
empty operation token
What it means
errEmptyOperationToken is a sentinel in common/nexus/nexusrpc/client.go indicating NewOperationHandle received an empty operation token. The token identifies a started asynchronous operation instance; without it the handle cannot address the operation, so it is rejected at construction time.
Source
Thrown at common/nexus/nexusrpc/client.go:43
// A function for making HTTP requests.
// Defaults to [http.DefaultClient.Do].
HTTPCaller func(*http.Request) (*http.Response, error)
// A [Serializer] to customize client serialization behavior.
// By default the client handles JSONables, byte slices, and nil.
Serializer nexus.Serializer
// A [FailureConverter] to convert a [Failure] instance to and from an [error]. Defaults to
// [DefaultFailureConverter].
FailureConverter FailureConverter
}
// User-Agent header set on HTTP requests.
const userAgent = "temporalio/server"
const headerUserAgent = "User-Agent"
var errEmptyOperationName = errors.New("empty operation name")
var errEmptyOperationToken = errors.New("empty operation token")
// UnexpectedResponseError indicates a client encountered something unexpected in the server's response.
type UnexpectedResponseError struct {
// Error message.
Message string
// Optional failure that may have been emedded in the response.
Failure *nexus.Failure
// Additional transport specific details.
// For HTTP, this would include the HTTP response. The response body will have already been read into memory and
// does not need to be closed.
Details any
}
// Error implements the error interface.
func (e *UnexpectedResponseError) Error() string {
return e.Message
}
View on GitHub (pinned to bde624efd1)
Solutions
- Ensure the token returned from a successful StartOperation call is propagated to NewOperationHandle
- Persist and restore the token (it is often base64-encoded) without dropping it
- Guard with errors.Is(err, nexusrpc.ErrEmptyOperationToken) to surface the cause
Example fix
// before
handle, _ := client.NewOperationHandle(ctx, op, savedToken) // savedToken was ""
// after
if savedToken == "" {
return nil, fmt.Errorf("operation token missing from stored handle")
}
handle, err := client.NewOperationHandle(ctx, op, savedToken) Defensive patterns
Strategy: validation
Validate before calling
func checkHandleArgs(operationName, token string) error {
if token == "" { return errors.New("operation token required") }
return nil
} Type guard
func hasOperationToken(token string) bool { return token != "" } Try / catch
handle, err := client.NewOperationHandle(ctx, opName, token)
if errors.Is(err, nexusrpc.ErrEmptyOperationToken) {
return fmt.Errorf("operation token missing: was the operation started and its token persisted?")
} Prevention
- Always capture the token from the StartOperation result immediately and persist it atomically
- Use nullable/explicit fields when serializing handles so empty tokens are detectable
- Never pass through zero-valued token variables; check before use
When it happens
Trigger: Calling NewOperationHandle with operationToken == "", e.g. a token that was never captured from a StartOperation response, or one lost through an empty JSON field when persisting the handle.
Common situations: Storing operation handles and reading back an empty token field; passing through unset variables from a StartOperation result; test coverage of failure conditions (TestNewHandleFailureConditions).
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- empty operation name
- link type is empty
- link type contains invalid char (valid chars: alphanumeric,
- ErrInvalidOperationToken
- second value out of range: %v
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/87f05dd4686e86f1.
Report an issue: GitHub.