go-redis/redis · error

connection is in invalid state for handoff

Error message

connection is in invalid state for handoff

What it means

Returned by performConnectionHandoff (handoff_worker.go:364) when conn.GetHandoffEndpoint() returns an empty string. A handoff was initiated for a connection, but no target endpoint was recorded, so there is nowhere to relocate it; shouldRetry=false so the connection is dropped rather than retried.

Source

Thrown at maintnotifications/errors.go:53

	ErrHandoffQueueFull = errors.New(logs.HandoffQueueFullError())
)

// Notification errors
var (
	// ErrInvalidNotification is returned when a notification is in an invalid format
	ErrInvalidNotification = errors.New(logs.InvalidNotificationError())
)

// connection handoff errors
var (
	// ErrConnectionMarkedForHandoff is returned when a connection is marked for handoff
	// and should not be used until the handoff is complete
	ErrConnectionMarkedForHandoff = errors.New(logs.ConnectionMarkedForHandoffErrorMessage)
	// ErrConnectionMarkedForHandoffWithState is returned when a connection is marked for handoff
	// and should not be used until the handoff is complete
	ErrConnectionMarkedForHandoffWithState = errors.New(logs.ConnectionMarkedForHandoffErrorMessage + " with state")
	// ErrConnectionInvalidHandoffState is returned when a connection is in an invalid state for handoff
	ErrConnectionInvalidHandoffState = errors.New(logs.ConnectionInvalidHandoffStateErrorMessage)
)

// shutdown errors
var (
	// ErrShutdown is returned when the maintnotifications manager is shutdown
	ErrShutdown = errors.New(logs.ShutdownError())
)

// circuit breaker errors
var (
	// ErrCircuitBreakerOpen is returned when the circuit breaker is open
	ErrCircuitBreakerOpen = errors.New(logs.CircuitBreakerOpenErrorMessage)
)

// circuit breaker configuration errors
var (
	// ErrInvalidCircuitBreakerFailureThreshold is returned when the circuit breaker failure threshold is invalid
	ErrInvalidCircuitBreakerFailureThreshold = errors.New(logs.InvalidCircuitBreakerFailureThresholdError())

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Capture the full MOVING payload from logs (logs.InvalidNewEndpointInMovingNotification / InvalidNotification) to confirm the server sent an endpoint.
  2. Check for any custom code that calls conn handoff-state setters and remove it; state should only be driven by the notification handler.
  3. Upgrade go-redis — an empty endpoint at handoff time usually indicates a state-machine race fixed in recent versions.
  4. If reproducing, file an issue with the conn ID and surrounding handoff log lines.
Defensive patterns

Strategy: fallback

Try / catch

if errors.Is(err, maintnotifications.ErrConnectionInvalidHandoffState) {
    // connection dropped; the next command uses a fresh connection. 
    // If recurring, capture MOVING payloads and report a possible state race.
    return retryCommand(ctx, cmd)
}

Prevention

When it happens

Trigger: A MOVING notification with a nil/empty newEndpoint led to a handoff being queued, but the connection's handoff endpoint field was never populated (or was cleared) by the time the worker ran performConnectionHandoff. This is an unexpected internal state — MOVING normally guarantees an endpoint or schedules a same-endpoint reconnect.

Common situations: A race where the connection's handoff state was reset between queuing and processing; a malformed MOVING that passed initial validation but carried no usable endpoint; concurrency between shutdown and handoff clearing the endpoint.

Related errors


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