nautechsystems/nautilus_trader · error
heartbeat health margin should be shorter than the safety…
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.
Solutions
- Restore the stock constant relationship: HEARTBEAT_HEALTH_MARGIN must be strictly less than HEARTBEAT_SAFETY_TIMEOUT.
- Rebuild from an unmodified nautilus source tree.
- 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
- Keep stock timing constants; if tuning, always preserve margin < safety timeout.
- Add a compile-time assert to catch bad constant edits.
- Rebuild adapters from unmodified upstream sources.
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- heartbeat request timeout should fit in an instant
- asset_id must be resolved for unsided cancellation
- ENVIO_API_TOKEN environment variable must be set
- Failed to create HyperSync client - check ENVIO_API_TOKEN…
- Invalid HyperSync URL
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/e14515ea1d4b1826.
Report an issue: GitHub.
Appendix: 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)