Skip to content

A refused lock-list refresh returns a bare status to the client, dropping upstream's body and Retry-After / LFS-Authenticate headers #64

Description

@matt-edmondson

What's wrong

When upstream refuses a lock-list refresh or probe, LockListRefresher keeps only the status code:

  • LockRefreshResult.Refused(response.StatusCode) at GitLfsCache/Locks/LockListRefresher.cs:63-65 (probe) and :94-97 (page walk).

LockListService turns that into LockListOutcome.Refuse(status) (LockListService.cs:102-105, 162-165). LockRouteHandler then writes only context.Response.StatusCode (GitLfsCache/Endpoints/LockRouteHandler.cs:61-64), with an empty body and none of upstream's headers.

The locks design spec says this should not happen. docs/superpowers/specs/2026-08-19-locks-subsystem-design.md:231: "Any upstream non-success during a refresh, a probe, or a relayed lock call is returned verbatim, status and body". The batch and object paths already relay verbatim through UpstreamRelay.CopyResponseAsync.

Why it matters

  • 429 or 403 with Retry-After: the client gets a bare 429/403 with no backoff hint, so it retries immediately and makes the rate limiting worse.
  • 401 with LFS-Authenticate / WWW-Authenticate: git-lfs uses these headers to decide it should prompt for or refresh credentials. Without them, the user sees an unexplained auth failure.
  • 403 with a JSON message: the reason, such as SSO enforcement or a missing scope, is lost.

Suggested fix

  • Carry the upstream body and non-hop-by-hop headers through LockRefreshResult and LockListOutcome. Either buffer them (lock error bodies are small) or keep the HttpResponseMessage alive.
  • Write them in LockRouteHandler with the same CopyResponseAsync helper the batch path uses.

Acceptance criteria

A stub upstream that answers GET locks with each of the following sends the client the same status, body, and headers when the listing is served through the snapshot path:

  • 429 + Retry-After: 30 + a JSON body
  • 401 + LFS-Authenticate

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions