neondatabase/neon · critical

Non-overlappinng bounds: self.max = {} was less than other.m

Error message

Non-overlappinng bounds: self.max = {} was less than other.min = {}

What it means

Mirror case of `ProtocolRange::highest_shared_version`: bails when `self.max < other.min`, i.e. this side's highest known version is below the peer's lowest acceptable version, so the ranges cannot overlap. Note the message contains a typo ('Non-overlappinng') — log greps must match the misspelling.

Source

Thrown at libs/vm_monitor/src/protocol.rs:225

            f.write_fmt(format_args!("{} to {}", self.min, self.max))
        }
    }
}

impl ProtocolRange {
    /// Find the highest shared version between two `ProtocolRange`'s
    pub fn highest_shared_version(&self, other: &Self) -> anyhow::Result<ProtocolVersion> {
        // We first have to make sure the ranges are overlapping. Once we know
        // this, we can merge the ranges by taking the max of the mins and the
        // mins of the maxes.
        if self.min > other.max {
            anyhow::bail!(
                "Non-overlapping bounds: other.max = {} was less than self.min = {}",
                other.max,
                self.min,
            )
        } else if self.max < other.min {
            anyhow::bail!(
                "Non-overlappinng bounds: self.max = {} was less than other.min = {}",
                self.max,
                other.min
            )
        } else {
            Ok(cmp::min(self.max, other.max))
        }
    }
}

/// We send this to the monitor after negotiating which protocol to use
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum ProtocolResponse {
    Error(String),
    Version(ProtocolVersion),
}

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Upgrade this component (the one whose max is too low) to a build supporting the peer's minimum version
  2. Or have the peer lower its minimum protocol version until this side catches up
  3. Grep logs for 'Non-overlappinng' (sic) and 'Non-overlapping' — both spellings occur on the two sides of the check
  4. Print both ProtocolRange values at handshake to confirm which pair is disjoint

Example fix

// before: peer requires >= v2.0, we only know up to v1.0
let my_range = ProtocolRange { min: PROTOCOL_MIN_VERSION, max: PROTOCOL_MAX_VERSION }; // v1.0..v1.0
my_range.highest_shared_version(&peer_min_v2_range)?; // bails: self.max < other.min

// after: upgrade so this side's max covers the peer's min
let my_range = ProtocolRange { min: PROTOCOL_MIN_VERSION, max: ProtocolVersion::V2_0 };
my_range.highest_shared_version(&peer_min_v2_range)?; // Ok(v2.0)
Defensive patterns

Strategy: validation

Validate before calling

fn ranges_overlap(a: &ProtocolRange, b: &ProtocolRange) -> bool {
    a.min <= b.max && a.max >= b.min
}

if !ranges_overlap(&my_range, &peer_range) {
    anyhow::bail!("peer requires {}, we only support up to {}", peer_range.min, my_range.max);
}

Type guard

fn ranges_overlap(a: &ProtocolRange, b: &ProtocolRange) -> bool {
    a.min <= b.max && a.max >= b.min
}

Prevention

When it happens

Trigger: Calling `highest_shared_version(&other)` where the peer demands a minimum version above this side's maximum — e.g. an upgraded agent with PROTOCOL_MIN_VERSION v2.0 talking to a monitor that only knows up to v1.0.

Common situations: Rolling upgrades where the new peer's minimum jumped past the old side's maximum; protocol constants drift between builds; tests with disjoint hardcoded ranges.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/16af108b6d8c757b. Report an issue: GitHub.