temporalio/temporal · error
reader with ID %v already exists
Error message
reader with ID %v already exists
What it means
ReaderGroup.newReaderLocked panics when a reader with the same numeric ID already exists in the group's readerMap. Reader IDs are unique keys for in-memory readers; GetOrCreateReader guards with a map lookup, so hitting this panic means a race or a bug bypassed the check — e.g. NewReader was called directly with a taken ID, or two goroutines raced without the group lock.
Source
Thrown at service/history/queues/reader_group.go:129
return nil, false
}
reader, ok := g.readerMap[readerID]
return reader, ok
}
func (g *ReaderGroup) NewReader(readerID int64, slices ...Slice) Reader {
g.Lock()
defer g.Unlock()
return g.newReaderLocked(readerID, slices...)
}
func (g *ReaderGroup) newReaderLocked(readerID int64, slices ...Slice) Reader {
reader := g.initializer(readerID, slices)
if _, ok := g.readerMap[readerID]; ok {
panic(fmt.Sprintf("reader with ID %v already exists", readerID))
}
g.readerMap[readerID] = reader
if g.isStarted() {
reader.Start()
}
return reader
}
func (g *ReaderGroup) RemoveReader(readerID int64) {
g.Lock()
defer g.Unlock()
reader, ok := g.readerMap[readerID]
if !ok {
return
}View on GitHub (pinned to bde624efd1)
Solutions
- Use GetOrCreateReader instead of NewReader so existing readers are returned rather than duplicated
- Ensure all reader creation goes through the ReaderGroup under its lock; never hold a stale reader reference that wasn't closed
- Check the reader lifecycle: confirm the reader with that ID was closed and removed from the group before reusing the ID
Example fix
// before reader := group.NewReader(readerID, slices...) // panics if ID exists // after reader := group.GetOrCreateReader(readerID, slices...)
Defensive patterns
Strategy: validation
Validate before calling
// use the group API instead of creating directly
if r := group.GetReader(readerID); r == nil {
r = group.GetOrCreateReader(readerID, slices...)
} Prevention
- Always create readers via GetOrCreateReader
- Never reuse a reader ID until the previous reader is closed and removed
- In tests, derive IDs uniquely (e.g. atomic counter) instead of constants
When it happens
Trigger: Calling ReaderGroup.NewReader(id, ...) with an ID that already exists; a race where GetOrCreateReader's locked check and creation interleave incorrectly; deterministic ID generation colliding after reader recycling.
Common situations: Custom code paths calling NewReader instead of GetOrCreateReader; ID reuse after a reader was expected to be closed but wasn't removed from the map; tests constructing multiple readers with constant IDs.
Related errors
- Can not append slice to existing list of slices, incoming sl
- Found overlapping incoming slices, left slice range: %v, rig
- Unable to split scope with range %v at %v
- Unable to merge scope with range %v with range %v by range
- Unable to merge scope with range %v with range %v by predica
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/77a3ced06c4d1fd2.
Report an issue: GitHub.