go-redis/redis · error

failed to enable maintnotifications: server does not…

Error message

failed to enable maintnotifications: server does not support RESP3 (HELLO command failed)

What it means

Thrown when maintenance notifications are explicitly required (MaintNotificationsConfig.Mode == ModeEnabled), Protocol is 3, but the HELLO command did not succeed — meaning the connection negotiated RESP2 and cannot receive RESP3 push frames. The client fails the connection fast rather than silently operating without maintenance notifications.

Solutions

  1. Switch MaintNotificationsConfig.Mode to ModeAuto (default) or ModeDisabled if the endpoint is known to be RESP2-only — the client will then silently disable maintnotifications instead of failing.
  2. Upgrade the target server to Redis 6+ (preferably 8.x) so HELLO/RESP3 and CLIENT MAINT_NOTIFICATIONS are supported.
  3. Confirm Options.Protocol is actually 3 and not being overridden; remove any proxy that strips RESP3.
  4. Validate connectivity to a RESP3-capable endpoint before constructing the client.

Example fix

// before
cfg := &redis.MaintNotificationsConfig{Mode: redis.ModeEnabled}
opt := &redis.Options{Addr: addr, Protocol: 3, MaintNotificationsConfig: cfg}
// server is RESP2-only -> connection fails

// after
cfg := &redis.MaintNotificationsConfig{Mode: redis.ModeAuto}
opt := &redis.Options{Addr: addr, Protocol: 3, MaintNotificationsConfig: cfg}
Defensive patterns

Strategy: validation

Validate before calling

// Detect RESP3/HELLO support before opting into ModeEnabled.
c := redis.NewClient(&redis.Options{Addr: addr, Protocol: 2})
var info map[string]string
_ = c.Hello(ctx, 3, "", "", "").Scan(&info)
c.Close()
if len(info) == 0 {
    return errors.New("server does not support RESP3; use ModeAuto/ModeDisabled")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        if strings.Contains(fmt.Sprint(r), "does not support RESP3") {
            // downgrade config and retry
        }
    }
}()
client := redis.NewClient(opt)

Prevention

When it happens

Trigger: Options.MaintNotificationsConfig.Mode = ModeEnabled, Options.Protocol = 3, and the server rejects or does not support HELLO (redis < 6, DragonflyDB, a proxy, or RESP2-only endpoint). Returned on every dial since maintnotification handshake is part of initConn.

Common situations: Pointing a ModeEnabled client at an older Redis (< 6) that has no HELLO; pointing at DragonflyDB or a proxy that speaks RESP2; connecting through a RESP2-terminating load balancer; REDIS_VERSION mismatch between configured image and actual server.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /api/errors/e98e75eb41ccec46. Report an issue: GitHub.

Appendix: source

Thrown at redis.go:927

		endpointType = c.opt.MaintNotificationsConfig.EndpointType
		maintNotifMode = c.opt.MaintNotificationsConfig.Mode
	}
	c.optLock.RUnlock()

	// Maintenance notifications require RESP3 push frames. If HELLO failed
	// and the connection fell back to RESP2, there is no point in sending
	// CLIENT MAINT_NOTIFICATIONS: the server either rejects it (making the
	// error misleading) or accepts it silently, leaving the client unable
	// to receive any notifications. Decide based on the actual negotiated
	// protocol rather than the requested one.
	if maintNotifEnabled && protocol == 3 && !helloOK {
		if maintNotifMode == maintnotifications.ModeEnabled {
			// Explicitly requested - fail fast with a clear reason.
			cn.GetStateMachine().Transition(pool.StateClosed)
			if errorCallback := pool.GetMetricErrorCallback(); errorCallback != nil {
				errorCallback(ctx, "HANDSHAKE_FAILED", cn, "HANDSHAKE_FAILED", true, 0)
			}
			return fmt.Errorf("failed to enable maintnotifications: server does not support RESP3 (HELLO command failed)")
		}
		// auto/other modes: silently disable maintnotifications for this client.
		c.optLock.Lock()
		c.opt.MaintNotificationsConfig.Mode = maintnotifications.ModeDisabled
		c.optLock.Unlock()
		if err := c.disableMaintNotificationsUpgrades(); err != nil {
			internal.Logger.Printf(ctx, "failed to disable maintnotifications in auto mode: %v", err)
		}
		maintNotifEnabled = false
	}

	var maintNotifHandshakeErr error
	if maintNotifEnabled && protocol == 3 {
		// Hold the manager read lock across the handshake and tracking so a
		// concurrent downgrade cannot remove pool-level listeners before a
		// successfully enabled connection is tracked for retirement.
		c.maintNotificationsManagerLock.RLock()
		manager := c.maintNotificationsManager

View on GitHub (pinned to 36d97525cd)