astrid-runtime/astrid · error
signed channel metadata has expired
Error message
signed channel metadata has expired
What it means
When `now` is supplied (always via parse_channel), the pointer must not yet be expired: the check requires `now <= expires_at`. Channel pointers are deliberately short-lived signed attestations; an expired pointer means the channel has not been republished within its validity window, so the client refuses to trust it for updates.
Solutions
- Re-fetch the channel pointer from the authoritative feed to get a fresh, unexpired publication
- Check the local system clock (`date`/NTP sync) — a clock far in the future makes valid pointers look expired
- If you operate the feed, fix and re-run the channel publishing workflow to renew expiry
- Clear any CDN/proxy cache that is serving stale channel metadata
Example fix
// before: using a stale cached pointer
let bytes = std::fs::read("cache/stable-channel.toml")?;
let ptr = parse_channel(&bytes, channel, Utc::now())?; // expired
// after: refresh from upstream first
let bytes = source.fetch_channel(UpdateChannel::Stable).await?;
let ptr = parse_channel(&bytes, channel, Utc::now())?; Defensive patterns
Strategy: retry
Validate before calling
fn pointer_is_current(p: &ChannelPointer, now: chrono::DateTime<Utc>) -> bool {
now <= p.expires_at
} Type guard
fn not_expired(p: &ChannelPointer, now: chrono::DateTime<Utc>) -> bool {
now <= p.expires_at
} Try / catch
let ptr = match parse_channel(&bytes, channel, Utc::now()) {
Err(e) if e.to_string().contains("has expired") => {
warn!("channel pointer expired; refetching");
let fresh = feed.fetch_channel(channel).await?;
parse_channel(&fresh, channel, Utc::now())?
}
other => other?,
}; Prevention
- Always fetch channel metadata fresh from upstream; avoid long-lived local caches
- Keep the system clock NTP-synced
- Monitor the publishing pipeline so expired channels are noticed before users hit them
- Set cache/CDN TTLs well below the channel's max lifetime
When it happens
Trigger: parse_channel(bytes, expected_channel, now) at a time later than the pointer's `expires_at`; calling against a stale mirror/cache; the publishing pipeline for that channel silently stopped producing new pointers.
Common situations: Client machine clock far ahead (wrong system time/RTC); a frozen CI cache or offline mirror serving an old channel.toml; the release automation has been broken for days so nobody republished stable.
Related errors
- FSKit lease is expired
- FUSE lease is expired
- 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/f7bdf9325e9a5969.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/update_channel.rs:483
&& pointer.product == PRODUCT
&& pointer.repository == REPOSITORY,
"signed channel identity is invalid"
);
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)?;View on GitHub (pinned to affd8760f4)