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

  1. Re-fetch the channel pointer from the authoritative feed to get a fresh, unexpired publication
  2. Check the local system clock (`date`/NTP sync) — a clock far in the future makes valid pointers look expired
  3. If you operate the feed, fix and re-run the channel publishing workflow to renew expiry
  4. 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

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


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)