nats-io/nats-server · error
unable to delete session %q record at sequence %v: %v
Error message
unable to delete session %q record at sequence %v: %v
What it means
Wraps an error from deleting the session's persisted record message in the '$MQTT.sess' stream by sequence. Sequence-not-found and msg-delete-failed-with-store-msg-not-found are deliberately ignored (benign in a cluster); anything else fails the cleanup.
Source
Thrown at server/mqtt.go:3499
for _, dur := range durs {
if _, err := sess.jsa.deleteConsumer(mqttStreamName, dur, noWait); isErrorOtherThan(err, JSConsumerNotFoundErr) {
return fmt.Errorf("unable to delete consumer %q for session %q: %v", dur, sess.id, err)
}
}
if pubRelDur != _EMPTY_ {
_, err := sess.jsa.deleteConsumer(mqttOutStreamName, pubRelDur, noWait)
if isErrorOtherThan(err, JSConsumerNotFoundErr) {
return fmt.Errorf("unable to delete consumer %q for session %q: %v", pubRelDur, sess.id, err)
}
}
if seq > 0 {
err := sess.jsa.deleteMsg(mqttSessStreamName, seq, !noWait)
// Ignore the various errors indicating that the message (or sequence)
// is already deleted, can happen in a cluster.
if isErrorOtherThan(err, JSSequenceNotFoundErrF) {
if isErrorOtherThan(err, JSStreamMsgDeleteFailedF) || !strings.Contains(err.Error(), ErrStoreMsgNotFound.Error()) {
return fmt.Errorf("unable to delete session %q record at sequence %v: %v", id, seq, err)
}
}
}
return nil
}
// This will update the session record for this client in the account's MQTT
// sessions stream if the session had any change in the subscriptions.
//
// Runs from the client's readLoop.
// Lock not held on entry, but session is in the locked map.
func (sess *mqttSession) update(filters []*mqttFilter, add bool) error {
// Evaluate if we need to persist anything.
var needUpdate bool
for _, f := range filters {
if add {
if f.qos == mqttSubAckFailure {
continueView on GitHub (pinned to 3a66a489d2)
Solutions
- Check JetStream health and that '$MQTT.sess' message deletion is permitted
- Retry the CONNECT once the cluster is stable
- Upgrade servers to consistent versions
- Inspect the wrapped cause for the precise JetStream API error
Example fix
// before // session replace fails deleting old record // after // restore JetStream quorum then reconnect; verify: nats stream info $MQTT.sess null
Defensive patterns
Strategy: retry
Try / catch
try {
await mqttConnect() // replaces previous session
} catch (e) {
if (String(e).includes('unable to delete session')) {
await waitForJetStreamHealthy()
await mqttConnect()
} else { throw e }
} Prevention
- Verify deletion is healthy on '$MQTT.sess' after upgrades
- Keep servers version-consistent in the cluster
- Treat session-not-found style errors as benign (server already does) and only escalate others
- Monitor wrapped cause errors in server logs
When it happens
Trigger: sess.jsa.deleteMsg(mqttSessStreamName, seq, !noWait) returns an error that is neither JSSequenceNotFoundErrF nor JSStreamMsgDeleteFailedF combined with ErrStoreMsgNotFound — e.g. permissions, API error, timeout, or stream unavailable.
Common situations: JetStream unavailable during session replacement (new CONNECT taking over a session); cluster instability; stream permission or internal API errors; server version mismatch in cluster.
Related errors
- loading session record: %w
- unable to persist session %q (seq=%v): %v
- ack wait must be a positive value
- JS API timeout must be a positive value
- mqtt requires JetStream to be enabled if running in standalo
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/72713662b7860649.
Report an issue: GitHub.