This error occurs when attempting to use the special $ ID with the XREADGROUP command. While $ represents the last entry in a stream, it cannot be used with XREADGROUP because consumer groups have different semantics for reading messages.
This error message appears when you try to use the special stream ID `$` with the XREADGROUP command in Redis Streams. The `$` ID represents "the last entry in the stream" and works perfectly with the XREAD command, but it has no meaningful interpretation in the context of consumer groups. XREADGROUP is designed to read messages from a consumer group, which maintains its own state of which messages have been delivered to consumers. When reading from a consumer group, you either want to read pending messages (by specifying an actual message ID) or fetch new messages that haven't been delivered to any consumer yet (by using the special `>` ID). The `$` ID doesn't fit either of these use cases. This error often confuses developers because Redis may suggest using `$` in error messages related to stream IDs, but that suggestion is contextual to XREAD, not XREADGROUP. Understanding the distinction between stream reading modes and consumer group semantics is key to avoiding this error.
Replace the $ ID with > to fetch only new messages that haven't been delivered to any consumer in the group:
# Incorrect - causes the error
XREADGROUP GROUP mygroup consumer1 STREAMS mystream $
# Correct - reads new messages only
XREADGROUP GROUP mygroup consumer1 STREAMS mystream >// Node.js with node-redis
const messages = await client.xReadGroup(
'mygroup',
'consumer1',
{ key: 'mystream', id: '>' }, // Use > not $
{ COUNT: 10, BLOCK: 5000 }
);The > special ID tells Redis to return only messages never delivered to other consumers so far, which is typically what you want when consuming from a group.
If you want to read the history of pending messages for this consumer, use a specific message ID or 0-0 to read from the beginning:
# Read pending messages from the beginning
XREADGROUP GROUP mygroup consumer1 STREAMS mystream 0-0
# Read pending messages from a specific ID
XREADGROUP GROUP mygroup consumer1 STREAMS mystream 1234567890123-0# Python with redis-py
messages = r.xreadgroup(
groupname='mygroup',
consumername='consumer1',
streams={'mystream': '0-0'}, # Read from beginning
count=10
)This retrieves messages that were previously delivered to this consumer but not yet acknowledged with XACK.
Remember that $ is valid for XREAD but not XREADGROUP:
XREAD with $:
# Valid - $ means "from the end of stream"
XREAD STREAMS mystream $XREADGROUP with >:
# Valid - > means "new messages not yet delivered"
XREADGROUP GROUP mygroup consumer1 STREAMS mystream >When creating a consumer group, you CAN use $ to set the starting position:
# Valid - consumer group starts from end of stream
XGROUP CREATE mystream mygroup $ MKSTREAMThe key distinction: $ sets a position during group creation, but > is used when reading from an existing group.
Ensure your consumer group is properly created before attempting to read:
# Create the consumer group first
XGROUP CREATE mystream mygroup 0 MKSTREAM
# Then read with >
XREADGROUP GROUP mygroup consumer1 STREAMS mystream >// Node.js - create group with error handling
try {
await client.xGroupCreate('mystream', 'mygroup', '0', {
MKSTREAM: true
});
} catch (err) {
if (!err.message.includes('BUSYGROUP')) {
throw err;
}
// Group already exists, continue
}
// Now read with >
const messages = await client.xReadGroup(
'mygroup',
'consumer1',
{ key: 'mystream', id: '>' },
{ COUNT: 10 }
);Consumer Group Semantics:
Consumer groups maintain a "last delivered ID" which tracks the position in the stream. When you use > with XREADGROUP, Redis returns messages with IDs greater than the last delivered ID and automatically advances this position. This is fundamentally different from XREAD's $, which simply means "the current end of the stream" without any state tracking.
Pending Entries List (PEL):
Every consumer group maintains a PEL containing message IDs that have been delivered but not yet acknowledged with XACK. If a consumer crashes, these pending messages can be reclaimed by other consumers. When you specify an actual ID (like 0-0) instead of >, you're reading from this PEL, not fetching new messages.
Special IDs Summary:
- > - XREADGROUP only: fetch new messages never delivered to any consumer
- $ - XREAD only: read from end of stream; also used in XGROUP CREATE to set starting position
- 0-0 - Both commands: read from beginning or read all pending messages (XREADGROUP)
- Actual ID (e.g., 1234567890123-0) - Both commands: read from specific position
Common Pattern:
Most consumer applications follow this pattern:
1. Create consumer group with XGROUP CREATE mystream mygroup $ MKSTREAM (start from end)
2. Continuously read with XREADGROUP ... STREAMS mystream > (get new messages)
3. Process messages and acknowledge with XACK mystream mygroup <message-id>
4. Never use $ in step 2 - always use >
Error Message Confusion:
Some Redis client libraries or error messages may suggest using $ when discussing stream IDs generically, but this advice doesn't apply to XREADGROUP. Always consult the specific command documentation: XREADGROUP has its own special ID semantics separate from XREAD.
ERR Unbalanced XREAD list of streams
How to fix "ERR Unbalanced XREAD list" in Redis
ERR syntax error
How to fix "ERR syntax error" in Redis
ConnectionError: Error while reading from socket
ConnectionError: Error while reading from socket in redis-py
ERR unknown command
How to fix ERR unknown command in Redis
Command timed out
How to fix 'Command timed out' in ioredis