nats-io/nats-server · error

stored session does not match client ID

Error message

stored session does not match client ID

What it means

This sentinel error (errMQTTSessionCollision) is returned by the NATS server's MQTT layer when a persisted MQTT session is restored from storage but its recorded client ID does not match the client ID of the connection attempting to resume it. The MQTT session is keyed by the client identifier, so a mismatch means the stored session belongs to a different logical client and cannot be safely reused; the code at server/mqtt.go:3177 compares ps.ID against clientID and fails the restore. Returning it prevents cross-client message/state leakage.

Source

Thrown at server/mqtt.go:247

	errMQTTServerNameMustBeSet        = errors.New("mqtt requires server name to be explicitly set")
	errMQTTUserMixWithUsersNKeys      = errors.New("mqtt authentication username not compatible with presence of users/nkeys")
	errMQTTTokenMixWIthUsersNKeys     = errors.New("mqtt authentication token not compatible with presence of users/nkeys")
	errMQTTAckWaitMustBePositive      = errors.New("ack wait must be a positive value")
	errMQTTJSAPITimeoutMustBePositive = errors.New("JS API timeout must be a positive value")
	errMQTTStandaloneNeedsJetStream   = errors.New("mqtt requires JetStream to be enabled if running in standalone mode")
	errMQTTConnFlagReserved           = errors.New("connect flags reserved bit not set to 0")
	errMQTTWillAndRetainFlag          = errors.New("if Will flag is set to 0, Will Retain flag must be 0 too")
	errMQTTPasswordFlagAndNoUser      = errors.New("password flag set but username flag is not")
	errMQTTCIDEmptyNeedsCleanFlag     = errors.New("when client ID is empty, clean session flag must be set to 1")
	errMQTTEmptyWillTopic             = errors.New("empty Will topic not allowed")
	errMQTTEmptyUsername              = errors.New("empty user name not allowed")
	errMQTTTopicIsEmpty               = errors.New("topic cannot be empty")
	errMQTTPacketIdentifierIsZero     = errors.New("packet identifier cannot be 0")
	errMQTTUnsupportedCharacters      = errors.New("character not supported for MQTT topics")
	errMQTTInvalidSession             = errors.New("invalid MQTT session")
	errMQTTInvalidRetainFlags         = errors.New("invalid retained message flags")
	errMQTTInvalidRetainedMessage     = errors.New("invalid retained message")
	errMQTTSessionCollision           = errors.New("stored session does not match client ID")
	errMQTTInvalidPublishLength       = errors.New("invalid publish message, variable header exceeds remaining length")
	errMQTTAckPipelineStopped         = errors.New("QoS1 PUBACK pipeline has shut down while admitting a message, " +
		"abandoning the wait for its JetStream ack; failing the connection, " +
		"the client will re-send unacknowledged PUBLISH packets on reconnect")
)

type srvMQTT struct {
	listener     net.Listener
	listenerErr  error
	authOverride bool
	sessmgr      mqttSessionManager
}

type mqttSessionManager struct {
	mu       sync.RWMutex
	sessions map[string]*mqttAccountSessionManager // key is account name
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Verify each MQTT client uses a unique, stable client ID and that the session store is keyed by that same client ID
  2. Check that the session persistence store is not shared or contaminated between servers/environments; use per-server or per-cluster namespaced storage
  3. If stale state is the cause, clear the stored session for that client ID (or have the client reconnect with Clean Session=true to purge it) and reconnect
  4. Upgrade/patch to a server version where the session store keying matches your deployment topology

Example fix

// before: reusing a shared store with colliding keys
client.Connect(mqtt.NewClientOptions().SetClientID("device-1").SetCleanSession(false))
// after: unique client ID per device + clean start after storage collision
client.Connect(mqtt.NewClientOptions().SetClientID("device-1-" + deviceUUID).SetCleanSession(false))
Defensive patterns

Strategy: validation

Validate before calling

// before connecting with CleanSession=false, ensure the client ID is unique per device
if clientID == "" || !strings.HasPrefix(clientID, deviceUUIDPrefix) {
    clientID = deviceUUIDPrefix + deviceUUID
}
// on collision, fall back to a clean session
opts := mqtt.NewClientOptions().SetClientID(clientID).SetCleanSession(firstAttempt == false)

Try / catch

// Go: treat the connack failure as a session-store issue
err := nc.Connect()
if err != nil && strings.Contains(err.Error(), "stored session does not match client ID") {
    // purge stored session for this client ID, then reconnect with Clean Session
    purgeStoredSession(clientID)
    reconnect(cleanSession = true)
}

Prevention

When it happens

Trigger: A client connects with clean session=false and a client ID whose stored session record maps to a different client ID (e.g. the session store was reused across servers, storage keys collided, or a store migration reassigned session keys), causing the ps.ID != clientID check in the session restore path to fail.

Common situations: Sharing one JetStream/stream bucket for MQTT sessions between multiple NATS servers without namespacing; manually editing or migrating persistent session state; resuming sessions after restoring a snapshot where session keys were regenerated; clients reusing a client ID after a server-side session store was rebuilt.

Related errors


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