astrid-runtime/astrid · error
second probe blocks
Error message
second probe blocks
What it means
Test assertion in `RedeemRateLimiter`: after a first successful `check(ip, interval)` returns `None` (allowed), the second call within the window must return `Some(wait)` — the remaining wait duration. The `expect("second probe blocks")` fires when the second probe was unexpectedly allowed (`None`), meaning the rate limiter failed to block a repeat request inside the interval.
Solutions
- Verify `check` records the IP's last-probe timestamp on the first call and compares `elapsed < interval` on the second.
- Confirm `RedeemRateLimiter::default()` actually initializes its map (not left empty after each check).
- Check the interval arithmetic uses a monotonic clock (`Instant`) rather than system time.
- Ensure tests don't share a global limiter state that another test reset.
Example fix
// before
let wait = limiter.check(ip, interval).expect("second probe blocks");
// after
let wait = limiter.check(ip, interval).unwrap_or_else(|| {
panic!("rate limiter allowed a second probe within {interval:?}; entries: {:?}", limiter.entries())
}); Defensive patterns
Strategy: validation
Validate before calling
// Rust: assert limiter state before second probe assert!(limiter.last_probe(&ip).is_some(), "first probe must be recorded");
Try / catch
// Rust: treat a None on a blocked-expected probe as a test failure with context
match limiter.check(ip, interval) {
Some(wait) => assert!(wait > Duration::ZERO),
None => panic!("second probe within {interval:?} was not blocked"),
} Prevention
- Use a monotonic clock (Instant) inside rate limiter implementations.
- Isolate limiter instances per test; never share global state.
- Cover clock-skew and window-reset behavior with dedicated tests.
- Keep limiter keying (IP + route) explicit and documented.
When it happens
Trigger: Calling `limiter.check(ip, interval)` twice for the same IP within a `Duration::from_mins(1)` window and getting `None` on the second call — the limiter did not record the first probe or reset its window incorrectly.
Common situations: A regression in `RedeemRateLimiter::default()` or `check` (e.g. keying by the wrong field, using wall-clock instead of instant comparison, or clearing entries per call); also system clock changes breaking interval math.
Related errors
- build_host_state
- own agent.v1.* event delivered (subtree match)
- absent migration source has a digest
- alice
- an incomplete capsule authority update exists at
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/07ffedcd3588b27d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/state.rs:499
Ok(
crate::bus_kernel::BusKernelClient::new(bus, caller.principal.clone(), session_id.0)
.with_device_key_id(caller.device_key_id.clone()),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_limiter_blocks_within_window() {
let mut limiter = RedeemRateLimiter::default();
let ip: IpAddr = "127.0.0.1".parse().unwrap();
let interval = Duration::from_mins(1);
assert!(limiter.check(ip, interval).is_none());
let wait = limiter.check(ip, interval).expect("second probe blocks");
assert!(wait > Duration::from_secs(0));
}
#[test]
fn rate_limiter_zero_interval_never_blocks() {
let mut limiter = RedeemRateLimiter::default();
let ip: IpAddr = "127.0.0.1".parse().unwrap();
let interval = Duration::from_secs(0);
// Zero interval: every probe should be free regardless.
assert!(limiter.check(ip, interval).is_none());
assert!(limiter.check(ip, interval).is_none());
}
#[test]
fn signing_material_round_trips() {
use ed25519_dalek::{Signer, Verifier};
let s = SigningMaterial::fresh();
let msg = b"hello world";View on GitHub (pinned to affd8760f4)