fix: clamp auto-wait budget to prevent Instant overflow panic

An unbounded --timeout-ms flowed into Instant::now() + Duration::from_millis(ms)
and panicked on overflow; with no catch_unwind on the CLI path this was a raw
crash on trivial input (e.g. --timeout-ms 99999999999999999999). Clamp the
budget to a 24h ceiling before deadline construction, with a regression test.
Also add APP_UNRESPONSIVE to the error-code as_str/serde consistency test, which
was silently non-exhaustive after the 16th variant landed.
This commit is contained in:
Lahfir 2026-07-02 22:39:14 -07:00
parent f329d5840f
commit 39cf1f0ad3
3 changed files with 17 additions and 1 deletions

View file

@ -316,6 +316,7 @@ mod tests {
(ErrorCode::NotificationNotFound, "NOTIFICATION_NOT_FOUND"),
(ErrorCode::SnapshotNotFound, "SNAPSHOT_NOT_FOUND"),
(ErrorCode::PolicyDenied, "POLICY_DENIED"),
(ErrorCode::AppUnresponsive, "APP_UNRESPONSIVE"),
(ErrorCode::Internal, "INTERNAL"),
];
for (code, expected) in cases {

View file

@ -40,6 +40,11 @@ fn trace_resolve_error(context: &CommandContext, ref_id: &str, err: &AdapterErro
pub(crate) const POLL_INTERVAL: Duration = Duration::from_millis(100);
pub(crate) const RESOLVE_ATTEMPT: Duration = Duration::from_millis(750);
const MAX_BUDGET_MS: u64 = 24 * 60 * 60 * 1000;
pub(crate) fn budget_from_ms(ms: u64) -> Duration {
Duration::from_millis(ms.min(MAX_BUDGET_MS))
}
pub(crate) fn execute_with_auto_wait(
adapter: &dyn PlatformAdapter,
@ -62,7 +67,7 @@ pub(crate) fn execute_with_auto_wait(
ref_id,
context,
request,
Duration::from_millis(budget_ms),
budget_from_ms(budget_ms),
dispatch,
)
}

View file

@ -8,6 +8,16 @@ use crate::{
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
#[test]
fn oversized_timeout_budget_is_clamped_and_never_overflows() {
assert_eq!(budget_from_ms(100), Duration::from_millis(100));
let clamped = budget_from_ms(u64::MAX);
assert!(
std::time::Instant::now().checked_add(clamped).is_some(),
"deadline construction must not overflow for an oversized --timeout-ms"
);
}
struct RetryAdapter {
resolve_calls: AtomicU32,
}