You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A refused lock-list refresh returns a bare status to the client, dropping upstream's body and Retry-After / LFS-Authenticate headers #64
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:
Priority: Medium. On the snapshot path, a refused lock listing reaches the client as a bare status. Dropping Retry-After makes rate limiting worse, and dropping LFS-Authenticate stops git-lfs from prompting for credentials. It also contradicts the locks spec's "returned verbatim, status and body".
Area / suggested assignment: Locks, in GitLfsCache/Locks/LockListRefresher.cs, LockListService.cs and GitLfsCache/Endpoints/LockRouteHandler.cs
Duplicates: none found among open issues in the org
Notes: Buffering the small error body and headers into LockRefreshResult/LockListOutcome, and writing them with the existing UpstreamRelay.CopyResponseAsync, keeps one relay path for batch, object and locks.
Activity
matt-edmondson commentedon Sep 28, 2026
Triage
Retry-Aftermakes rate limiting worse, and droppingLFS-Authenticatestops git-lfs from prompting for credentials. It also contradicts the locks spec's "returned verbatim, status and body".GitLfsCache/Locks/LockListRefresher.cs,LockListService.csandGitLfsCache/Endpoints/LockRouteHandler.csLockRouteHandler/LockListRefresher, so expect to rebase.Notes: Buffering the small error body and headers into
LockRefreshResult/LockListOutcome, and writing them with the existingUpstreamRelay.CopyResponseAsync, keeps one relay path for batch, object and locks.Generated by Claude Code