nsqio/nsq · error

E_BAD_BODY

E_BAD_BODY

Error message

heartbeat interval (%d) is invalid

What it means

During IDENTIFY negotiation, a client may ask for a custom heartbeat interval. clientV2.SetHeartbeatInterval accepts -1 (disable heartbeats), 0 (keep the nsqd default), or any millisecond value from 1000 up to --max-heartbeat-interval (default 60000ms). Anything else returns this error, which surfaces to the client as E_BAD_BODY, and the connection's IDENTIFY fails.

Source

Thrown at nsqd/client_v2.go:514

func (c *clientV2) UnPause() {
	c.tryUpdateReadyState()
}

func (c *clientV2) SetHeartbeatInterval(desiredInterval int) error {
	c.writeLock.Lock()
	defer c.writeLock.Unlock()

	switch {
	case desiredInterval == -1:
		c.HeartbeatInterval = 0
	case desiredInterval == 0:
		// do nothing (use default)
	case desiredInterval >= 1000 &&
		desiredInterval <= int(c.nsqd.getOpts().MaxHeartbeatInterval/time.Millisecond):
		c.HeartbeatInterval = time.Duration(desiredInterval) * time.Millisecond
	default:
		return fmt.Errorf("heartbeat interval (%d) is invalid", desiredInterval)
	}

	return nil
}

func (c *clientV2) SetOutputBuffer(desiredSize int, desiredTimeout int) error {
	c.writeLock.Lock()
	defer c.writeLock.Unlock()

	switch {
	case desiredTimeout == -1:
		c.OutputBufferTimeout = 0
	case desiredTimeout == 0:
		// do nothing (use default)
	case true &&
		desiredTimeout >= int(c.nsqd.getOpts().MinOutputBufferTimeout/time.Millisecond) &&
		desiredTimeout <= int(c.nsqd.getOpts().MaxOutputBufferTimeout/time.Millisecond):

View on GitHub (pinned to 85cf10c09c)

Solutions

  1. Send a value in the accepted window: 1000 <= v <= 60000 (milliseconds), or 0 for the server default, or -1 to disable
  2. If you genuinely need intervals above 60s, raise --max-heartbeat-interval on nsqd
  3. Double-check the unit: the field is milliseconds, not seconds or microseconds
  4. Re-check your client library's IDENTIFY struct after upgrades that changed field semantics

Example fix

// go-nsq consumer config
// before
cfg.HeartbeatInterval = 90 * time.Second // sent as 90000ms > 60s max -> E_BAD_BODY

// after
cfg.HeartbeatInterval = 30 * time.Second // 30000ms, within [1000,60000]
Defensive patterns

Strategy: validation

Validate before calling

func clampHeartbeat(ms int, maxMs int) int {
	switch {
	case ms == -1 || ms == 0:
		return ms
	case ms < 1000:
		return 0 // fall back to server default
	case ms > maxMs:
		return maxMs
	}
	return ms
}

Try / catch

// client libraries surface this as an IDENTIFY error on connect:
// catch the connect/identify error, log the exact heartbeat value, fix and reconnect

Prevention

When it happens

Trigger: Sending heartbeat: 500 (below the 1s floor), heartbeat: -5 (only exactly -1 is special), heartbeat: 120000 when max is 60s, or passing microseconds/seconds where milliseconds are expected (unit confusion is the classic case).

Common situations: Porting a client from another broker whose heartbeat unit is seconds (e.g. sending 30 meaning 30s, which passes validation but means 30ms of the wrong scale, or sending 90000 for 90s and tripping the max); tuning liveness for long-idle consumers above the server max.

Related errors


AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16). Data as JSON: /api/errors/96848eb4136805c9. Report an issue: GitHub.