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
- Set InclusiveMaxMessageMetadata.ID to a valid message ID >= FirstQueueMessageID before calling.
- Obtain the actual last message ID from the queue (e.g., from GetQueueMetadata/message count APIs) instead of a hardcoded 0.
- 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
- Never pass zero-value request structs; always populate the inclusive max message metadata.
- Fetch the current max message ID from queue metadata before range deletes.
- Add caller-side assertions on message IDs before persistence calls.
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
- link type is empty
- link type contains invalid char (valid chars: alphanumeric,
- empty operation name
- empty operation token
- membershipExpiry duration should be atleast 1 second
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/1d0d80f15a270230.
Report an issue: GitHub.