nautechsystems/nautilus_trader · error

heartbeat health margin should be shorter than the safety ti

Error message

heartbeat health margin should be shorter than the safety timeout

What it means

The Polymarket heartbeat task computes its health-check timeout as HEARTBEAT_SAFETY_TIMEOUT - HEARTBEAT_HEALTH_MARGIN using checked_sub on Durations. The expect asserts the compile-time constant margin is smaller than the safety timeout; with any valid build this cannot fail, so the panic indicates corrupted constants or a binary/patch tampering with them.

Source

Thrown at crates/adapters/polymarket/src/execution/lifecycle.rs:737

        self.teardown_partial_connect().await?;

        log::info!("Disconnected: client_id={}", self.core.client_id);
        Ok(())
    }

    pub(super) fn on_instrument_update(&self, instrument: &InstrumentAny) {
        self.upsert_execution_lookup(instrument);
    }
}

async fn run_heartbeats(
    http_client: crate::http::clob::PolymarketClobHttpClient,
    cancellation: CancellationToken,
    healthy: Arc<AtomicBool>,
) {
    let heartbeat_health_timeout = HEARTBEAT_SAFETY_TIMEOUT
        .checked_sub(HEARTBEAT_HEALTH_MARGIN)
        .expect("heartbeat health margin should be shorter than the safety timeout");
    let mut interval = tokio::time::interval(HEARTBEAT_INTERVAL);
    interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
    let mut heartbeat_id = String::new();
    let mut request_failures = 0;
    let mut last_acknowledged = None;

    loop {
        tokio::select! {
            () = cancellation.cancelled() => break,
            _ = interval.tick() => {}
        }

        let mut resynchronized = false;

        loop {
            let now = tokio::time::Instant::now();
            let request_timeout = now
                .checked_add(HEARTBEAT_REQUEST_TIMEOUT)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Restore the stock constant relationship: HEARTBEAT_HEALTH_MARGIN must be strictly less than HEARTBEAT_SAFETY_TIMEOUT.
  2. Rebuild from an unmodified nautilus source tree.
  3. If tuning constants, add/keep a static assert or startup check validating margin < safety timeout.

Example fix

// before
const HEARTBEAT_HEALTH_MARGIN: Duration = Duration::from_secs(20);
const HEARTBEAT_SAFETY_TIMEOUT: Duration = Duration::from_secs(15); // margin > safety

// after
const HEARTBEAT_SAFETY_TIMEOUT: Duration = Duration::from_secs(30);
const HEARTBEAT_HEALTH_MARGIN: Duration = Duration::from_secs(10); // margin < safety
Defensive patterns

Strategy: validation

Validate before calling

// build-time/static check if you tune the constants
const _: () = assert!(HEARTBEAT_HEALTH_MARGIN < HEARTBEAT_SAFETY_TIMEOUT);

Prevention

When it happens

Trigger: Starting the heartbeat task (start_heartbeat_task) when HEARTBEAT_HEALTH_MARGIN >= HEARTBEAT_SAFETY_TIMEOUT — only possible if the constants were modified (e.g. local patch or a mis-scoped fork build).

Common situations: Forked adapter builds where timing constants were tweaked without preserving the invariant margin < safety timeout; binary-level constant corruption.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/e14515ea1d4b1826. Report an issue: GitHub.