nats-io/nats-server · warning

gsl: no matches found

Error message

gsl: no matches found

What it means

ErrNotFound ('gsl: no matches found') is returned by the sublist remove path when the value being removed does not exist in the subject tree. gsl.go:388 returns it when traversal hits a nil node (no matching path), and gsl.go:412 returns it when removeFromNode finds no exact match for the stored value.

Source

Thrown at server/gsl/gsl.go:41

// provides a facility to match subjects from published messages to
// interested subscribers. Subscribers can have wildcard subjects to
// match multiple published subjects.

// Common byte variables for wildcards and token separator.
const (
	pwc     = '*'
	pwcs    = "*"
	fwc     = '>'
	fwcs    = ">"
	tsep    = "."
	btsep   = '.'
	_EMPTY_ = ""
)

// Sublist related errors
var (
	ErrInvalidSubject    = errors.New("gsl: invalid subject")
	ErrNotFound          = errors.New("gsl: no matches found")
	ErrNilChan           = errors.New("gsl: nil channel")
	ErrAlreadyRegistered = errors.New("gsl: notification already registered")
)

// SimpleSublist is an alias type for GenericSublist that takes
// empty values, useful for tracking interest only without any
// unnecessary allocations.
type SimpleSublist = GenericSublist[struct{}]

// NewSimpleSublist will create a simple sublist.
func NewSimpleSublist() *SimpleSublist {
	return &GenericSublist[struct{}]{root: newLevel[struct{}]()}
}

// A GenericSublist stores and efficiently retrieves subscriptions.
type GenericSublist[T comparable] struct {
	sync.RWMutex
	root  *level[T]

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Verify the exact subject and value used at insertion; remove must match both subject path and stored value.
  2. Treat ErrNotFound on removal as benign idempotent cleanup where possible, or track insertion state to skip redundant removes.
  3. Guard against double-unsubscribe in client shutdown paths (check a 'removed' flag before calling remove).

Example fix

// before
sl.Remove(subject, sub) // may return gsl: no matches found
// after
if err := sl.Remove(subject, sub); err != nil && !errors.Is(err, gsl.ErrNotFound) {
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// track whether the interest was inserted before removing
if !inserted[subject] {
    return nil // nothing to remove
}

Try / catch

if err := sl.Remove(subject, value); err != nil && !errors.Is(err, gsl.ErrNotFound) {
    return err // real failure; NotFound is idempotent
}

Prevention

When it happens

Trigger: Calling Remove/RemoveBatch on a subject that was never inserted, or removing with a different value than the one registered at an existing subject node (removeFromNode returns false); also removing after the interest was already deleted.

Common situations: Unsubscribe with a slightly different subject than the subscribe (trailing wildcard mismatch), double-unsubscribe in cleanup code, removal racing with an earlier removal, clients tracked in maps that were already cleaned up.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/9f534e070932ff93. Report an issue: GitHub.