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
- Send a value in the accepted window: 1000 <= v <= 60000 (milliseconds), or 0 for the server default, or -1 to disable
- If you genuinely need intervals above 60s, raise --max-heartbeat-interval on nsqd
- Double-check the unit: the field is milliseconds, not seconds or microseconds
- 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
- Configure heartbeats through the client library's Duration field, not raw ints
- Confirm the server's --max-heartbeat-interval before raising client values
- Unit-test client IDENTIFY payloads against the documented [1000, max] window
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.