nats-io/nats-server · error

another session is in use with client ID %q

Error message

another session is in use with client ID %q

What it means

Returned by mqttAccountSessionManager.lockSession when a session for a given client ID cannot be acquired because it is already 'locked' by another in-flight client connection, or the session is currently bound to a different client (sess.c != c). The server rejects the second CONNECT takeover attempt so two live clients never share one MQTT session.

Source

Thrown at server/mqtt.go:2517

// First check if this session's client ID is already in the "locked" map,
// which if it is the case means that another client is now bound to this
// session and this should return an error.
// If not in the "locked" map, but the client is not bound with this session,
// then same error is returned.
// Finally, if all checks ok, then the session's ID is added to the "locked" map.
//
// No lock held on entry.
func (as *mqttAccountSessionManager) lockSession(sess *mqttSession, c *client) error {
	as.mu.Lock()
	defer as.mu.Unlock()
	var fail bool
	if _, fail = as.sessLocked[sess.id]; !fail {
		sess.mu.Lock()
		fail = sess.c != c
		sess.mu.Unlock()
	}
	if fail {
		return fmt.Errorf("another session is in use with client ID %q", sess.id)
	}
	as.sessLocked[sess.id] = struct{}{}
	return nil
}

// Remove the session from the "locked" map.
//
// No lock held on entry.
func (as *mqttAccountSessionManager) unlockSession(sess *mqttSession) {
	as.mu.Lock()
	delete(as.sessLocked, sess.id)
	as.mu.Unlock()
}

// Simply adds the session to the various sessions maps.
// The boolean `lock` indicates if this function should acquire the lock
// prior to adding to the maps.
//

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure each client uses a unique client ID
  2. Retry the CONNECT after a short backoff so the old session can be unlocked (unlockSession runs on disconnect)
  3. Investigate why the old connection lingers (half-open TCP, missing keepalive/disconnect) and reduce that window
  4. If a client is stuck, close the old TCP connection to force session release

Example fix

// before
connect(client, clientID="device-1", reconnectDelay=0)
// after
connect(client, clientID=uuid(), reconnectDelay=2s + jitter) // or retry same ID after backoff
Defensive patterns

Strategy: retry

Validate before calling

// Ensure client ID uniqueness before connecting
if clientID == "" || !/^[A-Za-z0-9_-]{1,64}$/.test(clientID) {
  clientID = `client-${crypto.randomUUID()}`
}
if (activeConnections.has(clientID)) throw new Error('client ID already connected')

Try / catch

// catch the CONNECT error and retry with backoff
try {
  conn = await connect({ clientID })
} catch (e) {
  if (String(e).includes('another session is in use')) {
    await sleep(backoff++)
    conn = await connect({ clientID })
  } else { throw e }
}

Prevention

When it happens

Trigger: Two MQTT clients connect concurrently with the same client ID; a previous connection for that client ID has not fully torn down when the new CONNECT is processed (lock still held in sessLocked, or sess.c still points at the old client).

Common situations: Load balancer or test harness reconnecting faster than the old TCP connection is cleaned up; duplicated client ID in a device fleet; a stuck/zombie TCP half-open connection keeping the old session bound.

Related errors


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