Skip to content

An upstream that sends Go's zero time as expires_at ("0001-01-01T00:00:00Z") is never cached: every action is left pointing at upstream #142

Closed

Description

@matt-edmondson

What's wrong

BatchRewriter.ActionExpiry (GitLfsCache/Batch/BatchRewriter.cs, ~L229-254) reads any parseable expires_at as a real deadline. Go LFS servers that declare ExpiresAt time.Time with json:",omitempty" still emit the zero value, because omitempty has no effect on a struct type, so the action arrives as:

"download": { "href": "...", "expires_at": "0001-01-01T00:00:00Z" }

The git-lfs client treats a zero expires_at as "no expiry". Its expiry check (tools.IsExpiredAtOrIn) skips a zero time. The proxy reads it as an expiry that has already passed:

  1. System.Text.Json parses the value as DateTimeOffset.MinValue.
  2. LessMargin sees remaining <= 0 and returns MinValue unchanged.
  3. Earliest(proxyExpiresAt, MinValue) gives MinValue, so actionExpiresInSeconds = Max(0, …) = 0.
  4. The if (actionExpiresInSeconds <= 0) return; guard added in 246781e leaves the href and header pointing at upstream.

Why it matters

Behind such an upstream, every download and upload goes straight to the origin and nothing is ever cached. Nothing reports an error, and the only sign is that the cache hit metrics never move. Before 246781e the same input produced tokens that had already expired, so every transfer was refused with a 403. The recent fix changed the symptom but did not fix the cause.

Suggested fix

  • In ActionExpiry, treat an expires_at equal to DateTimeOffset.MinValue (the zero time) as absent, which matches git-lfs.
  • Optional, for a maintainer to decide: git-lfs also reads expires_in: 0 as "no expiry". Treating it as absent would contradict the existing test Rewrite_UpstreamHasNoWholeSecondLeft_LeavesTheActionPointingAtUpstream, so that is a deliberate choice.

Acceptance criteria

  • A batch response whose action carries "expires_at": "0001-01-01T00:00:00Z" gets its href rewritten to the proxy, its header removed, and expires_in == TokenLifetime.
  • Real past and near-future expires_at values behave exactly as they do today.

Activity

  1. matt-edmondson commented on Oct 30, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    • Category: Bug
    • Priority: High. Behind any Go-based LFS upstream that emits the zero expires_at, the proxy silently stops caching anything. Every transfer goes straight to origin, nothing errors, and only flat cache-hit metrics show it. Data stays correct, but the cache's whole purpose fails without any sign. The fix is small and local.
    • Area / suggested assignment: Batch rewriting: BatchRewriter.ActionExpiry (GitLfsCache/Batch/BatchRewriter.cs, ~L229-254). Treat DateTimeOffset.MinValue as absent before LessMargin/Earliest, and add a rewrite test for "0001-01-01T00:00:00Z". Whether expires_in: 0 should also mean "no expiry" is a separate maintainer call, because it contradicts Rewrite_UpstreamHasNoWholeSecondLeft_LeavesTheActionPointingAtUpstream.
    • Duplicates / related: No duplicate found. This is a follow-up to 246781e, which turned the earlier 403 symptom into silent pass-through.
    • In progress: No open GitLfsCache PR addresses this. Keep a configured Kestrel endpoint unless --port is given [patch] #141 (Kestrel endpoint) and Replace a request header with the batch action's value instead of adding a second one #129 (header replacement) touch other code.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions