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
What's wrong
When upstream refuses a lock-list refresh or probe,
LockListRefresherkeeps only the status code:LockRefreshResult.Refused(response.StatusCode)atGitLfsCache/Locks/LockListRefresher.cs:63-65(probe) and:94-97(page walk).LockListServiceturns that intoLockListOutcome.Refuse(status)(LockListService.cs:102-105, 162-165).LockRouteHandlerthen writes onlycontext.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 throughUpstreamRelay.CopyResponseAsync.Why it matters
Retry-After: the client gets a bare 429/403 with no backoff hint, so it retries immediately and makes the rate limiting worse.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.message: the reason, such as SSO enforcement or a missing scope, is lost.Suggested fix
LockRefreshResultandLockListOutcome. Either buffer them (lock error bodies are small) or keep theHttpResponseMessagealive.LockRouteHandlerwith the sameCopyResponseAsynchelper the batch path uses.Acceptance criteria
A stub upstream that answers
GET lockswith each of the following sends the client the same status, body, and headers when the listing is served through the snapshot path:Retry-After: 30+ a JSON bodyLFS-Authenticate