temporalio/temporal · error

%w: id is %d but must be >= %d

Error message

%w: id is %d but must be >= %d

What it means

RangeDeleteMessages validates that InclusiveMaxMessageMetadata.ID is at least persistence.FirstQueueMessageID; if a smaller (e.g., zero or negative) ID is passed, it returns ErrInvalidQueueRangeDeleteMaxMessageID wrapped with the offending value. It is a pure input-validation guard before any database work.

Source

Thrown at common/persistence/sql/queue_v2.go:227

		)
	}
	if err != nil {
		return nil, serviceerror.NewUnavailablef(
			"CreateQueue failed for queue with type: %v and name: %v. InsertIntoQueueV2Metadata operation failed. Error: %v",
			request.QueueType,
			request.QueueName,
			err,
		)
	}
	return &persistence.InternalCreateQueueResponse{}, nil
}

func (q *queueV2) RangeDeleteMessages(
	ctx context.Context,
	request *persistence.InternalRangeDeleteMessagesRequest,
) (*persistence.InternalRangeDeleteMessagesResponse, error) {
	if request.InclusiveMaxMessageMetadata.ID < persistence.FirstQueueMessageID {
		return nil, fmt.Errorf(
			"%w: id is %d but must be >= %d",
			persistence.ErrInvalidQueueRangeDeleteMaxMessageID,
			request.InclusiveMaxMessageMetadata.ID,
			persistence.FirstQueueMessageID,
		)
	}
	var resp *persistence.InternalRangeDeleteMessagesResponse
	err := q.txExecute(ctx, "RangeDeleteMessages", func(tx sqlplugin.Tx) error {
		qm, err := q.getQueueMetadata(ctx, tx, request.QueueType, request.QueueName)
		if err != nil {
			return err
		}
		partition, err := persistence.GetPartitionForQueueV2(request.QueueType, request.QueueName, qm)
		if err != nil {
			return serviceerror.NewUnavailablef(
				"RangeDeleteMessages failed for queue with type: %v and name: %v. GetPartitionForQueueV2 operation failed. Error: %v",
				request.QueueType,
				request.QueueName,

View on GitHub (pinned to bde624efd1)

Solutions

  1. Set InclusiveMaxMessageMetadata.ID to a valid message ID >= FirstQueueMessageID before calling.
  2. Obtain the actual last message ID from the queue (e.g., from GetQueueMetadata/message count APIs) instead of a hardcoded 0.
  3. If deleting everything, fetch the current max message ID and pass that as the inclusive max.

Example fix

// before
req := &persistence.InternalRangeDeleteMessagesRequest{} // ID defaults to 0
store.RangeDeleteMessages(ctx, req)
// after
req := &persistence.InternalRangeDeleteMessagesRequest{
    InclusiveMaxMessageMetadata: &persistencespb.QueueMessageMetadata{ID: actualMaxID},
}
store.RangeDeleteMessages(ctx, req)
Defensive patterns

Strategy: validation

Validate before calling

if req.InclusiveMaxMessageMetadata == nil || req.InclusiveMaxMessageMetadata.ID < persistence.FirstQueueMessageID {
    return fmt.Errorf("inclusive max message ID must be >= %d", persistence.FirstQueueMessageID)
}
// safe to call RangeDeleteMessages

Try / catch

if errors.Is(err, persistence.ErrInvalidQueueRangeDeleteMaxMessageID) {
    // fix caller: fetch real max message ID and retry once
}

Prevention

When it happens

Trigger: Calling RangeDeleteMessages with InternalRangeDeleteMessagesRequest.InclusiveMaxMessageMetadata.ID < FirstQueueMessageID (e.g., 0 from an uninitialized struct or a default-valued message ID).

Common situations: Caller passes a zero-value struct with the max-message ID never populated; off-by-one arithmetic producing 0; deserializing a request whose ID field was never set.

Related errors


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