astrid-runtime/astrid · error
signed channel published-at is unreasonably far in the…
Error message
signed channel published-at is unreasonably far in the future
What it means
The pointer's `published_at` must not be more than MAX_FUTURE_SKEW_SECS ahead of the caller's current time. A publication timestamp far in the future indicates a badly generated metadata file or a client clock that is far behind, either of which would break freshness and expiry reasoning, so it is rejected.
Solutions
- Sync the local clock via NTP (`timedatectl set-ntp true`) and retry
- Check the publishing tool writes `published-at` as UTC RFC 3339 (e.g. `2026-09-09T12:00:00Z`)
- Re-generate the channel pointer with a correct timestamp if you produced it
- Verify no offset was dropped when serializing chrono DateTime to the TOML
Example fix
// before: naive local time serialized without offset published-at = "2026-09-09T12:00:00" // after published-at = "2026-09-09T12:00:00Z"
Defensive patterns
Strategy: validation
Validate before calling
fn publish_time_is_reasonable(p: &ChannelPointer, now: chrono::DateTime<Utc>) -> bool {
p.published_at <= now + chrono::Duration::seconds(MAX_FUTURE_SKEW_SECS)
} Type guard
fn published_not_in_future(p: &ChannelPointer, now: chrono::DateTime<Utc>) -> bool {
p.published_at <= now + chrono::Duration::seconds(MAX_FUTURE_SKEW_SECS)
} Try / catch
match parse_channel(&bytes, channel, Utc::now()) {
Err(e) if e.to_string().contains("unreasonably far in the future") => {
anyhow::bail!("local clock is likely wrong or metadata timestamp is bad; sync clock and retry")
}
other => other?,
} Prevention
- Publishers must serialize UTC RFC 3339 timestamps with explicit offset
- Run NTP on both publisher and client machines
- Refuse to publish if published_at is ahead of the publisher's own clock
- Sanity-check timestamps in CI before signing
When it happens
Trigger: parse_channel or enforce_continuity sees `published_at > now + MAX_FUTURE_SKEW_SECS`; usually a publisher writing local (non-UTC) time misinterpreted as UTC, or the client's clock set months/years behind.
Common situations: Machine with wrong year/RTC battery dead (clock reset to 2020) making a 2026 publish look absurdly future; publisher script formatting a naive datetime without timezone; fabricated metadata.
Related errors
- signed channel lifetime exceeds the maximum for its channel
- signed channel lifetime is invalid
- must contain exactly targets
- release metadata does not bind the authenticated legacy…
- release metadata does not match the authenticated legacy…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/369a0c38e6ff78a0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/update_channel.rs:487
ensure!(
pointer.channel == expected_channel.as_str(),
"signed channel names '{}', expected '{}'",
pointer.channel,
expected_channel.as_str()
);
ensure!(
pointer.generation > 0,
"signed channel generation must be positive"
);
let published = canonical_time(&pointer.published_at, "published-at")?;
let expires = canonical_time(&pointer.expires_at, "expires-at")?;
ensure!(expires > published, "signed channel lifetime is invalid");
if let Some(now) = now {
ensure!(now <= expires, "signed channel metadata has expired");
let latest_reasonable_publication = now
.checked_add_signed(chrono::Duration::seconds(MAX_FUTURE_SKEW_SECS))
.context("channel publication skew overflowed the clock")?;
ensure!(
published <= latest_reasonable_publication,
"signed channel published-at is unreasonably far in the future"
);
}
let max_lifetime = match expected_channel {
UpdateChannel::Stable => chrono::Duration::days(30),
UpdateChannel::Dev => chrono::Duration::days(7),
UpdateChannel::Nightly => chrono::Duration::days(2),
};
ensure!(
expires.signed_duration_since(published) <= max_lifetime,
"signed channel lifetime exceeds the maximum for its channel"
);
let version = canonical_version(&pointer.release.version)?;
let nightly_commit = nightly_source_commit(&version);
match expected_channel {
UpdateChannel::Nightly => ensure!(
nightly_commit.is_some() && version.build.is_empty(),View on GitHub (pinned to affd8760f4)