{"thread":{"id":"64473","subject":"[PATCH] remote-curl: Use auth for probe_rpc() requests too","startedAt":"2025-11-12T22:37:59Z","lastAt":"2026-01-12T08:22:02Z","messageCount":6,"participants":["Aaron Plattner","Lucas De Marchi","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"530630","messageId":"20251112223722.376330-1-aplattner@nvidia.com","threadId":"64473","inReplyTo":null,"subject":"[PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Aaron Plattner","fromEmail":"aplattner@nvidia.com","sentAt":"2025-11-12T22:37:18Z","receivedAt":"2025-11-12T22:37:59Z","isPatch":true,"sender":{"key":"aplattner@nvidia.com","avatar":"https://avatars.githubusercontent.com/u/343551?v=4"},"body":"If a large request requires post_rpc() to call probe_rpc(), the latter\ndoes not use the authorization credentials used for other requests. If\nthis fails with an HTTP 401 error and http_auth.multistage isn't set,\nthen the whole request just fails.\n\nFor example, using git-credential-msal [1], the following attempt to clone a\nlarge repository fails partway through because the initial request to download\nthe commit history and promisor packs succeeds, but the\nsubsequent request to download the blobs needed to construct the working\ntree fails with a 401 error and the checkout fails.\n\n(lines removed for brevity)\n\n  git clone --filter=blob:none https://secure-server.example/repo\n  11:03:26.855369 git.c:502               trace: built-in: git clone --filter=blob:none https://secure-server.example/repo\n  Cloning into 'sw'...\n  warning: templates not found in /home/aaron/share/git-core/templates\n  11:03:26.857169 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n  11:03:27.012104 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n  11:03:27.049243 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n  11:03:27.049270 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n  11:03:27.053786 run-command.c:673       trace: run_command: 'git credential-msal get'\n  11:03:27.952830 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n  11:03:27.952849 http.c:849              => Send header: Authorization: Bearer <redacted>\n  11:03:27.995419 http.c:849              <= Recv header: HTTP/1.1 200 OK\n  11:03:28.230039 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n  11:03:28.230208 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n  11:03:28.230216 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n  11:03:28.230221 http.c:849              => Send header: Authorization: Bearer <redacted>\n  11:03:28.269085 http.c:849              <= Recv header: HTTP/1.1 200 OK\n  11:03:28.684163 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n  11:03:28.684379 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n  11:03:28.684391 http.c:849              => Send header: Accept: application/x-git-upload-pack-result\n  11:03:28.684393 http.c:849              => Send header: Authorization: Bearer <redacted>\n  11:03:28.869546 run-command.c:673       trace: run_command: git index-pack --stdin --fix-thin '--keep=fetch-pack 43856 on dgx-spark' --promisor\n  11:06:39.861237 run-command.c:673       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin\n  11:06:39.865981 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n  11:06:39.868039 run-command.c:673       trace: run_command: git-remote-https origin https://secure-server.example/repo\n  11:07:30.412575 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n  11:07:30.456285 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n  11:07:30.456318 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n  11:07:30.456439 run-command.c:673       trace: run_command: 'git credential-cache get'\n  11:07:30.461266 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n  11:07:30.461282 http.c:849              => Send header: Authorization: Bearer <redacted>\n  11:07:30.501628 http.c:849              <= Recv header: HTTP/1.1 200 OK\n  11:07:34.725262 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n  11:07:34.725279 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n  11:07:34.761407 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n  11:07:34.761443 http.c:890              == Info: Bearer authentication problem, ignoring.\n  11:07:34.761453 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n  11:07:34.761509 http.c:890              == Info: The requested URL returned error: 401\n  11:07:34.761530 http.c:890              == Info: closing connection #0\n  11:07:34.761913 run-command.c:673       trace: run_command: 'git credential-cache erase'\n  11:07:34.761927 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-cache erase' 'git credential-cache erase'\n  11:07:34.768069 git.c:502               trace: built-in: git credential-cache erase\n  11:07:34.768690 run-command.c:673       trace: run_command: 'git credential-msal erase'\n  11:07:34.768713 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-msal erase' 'git credential-msal erase'\n  11:07:34.772742 git.c:808               trace: exec: git-credential-msal erase\n  11:07:34.772783 run-command.c:673       trace: run_command: git-credential-msal erase\n  11:07:34.772819 run-command.c:765       trace: start_command: /usr/bin/git-credential-msal erase\n  error: RPC failed; HTTP 401 curl 22 The requested URL returned error: 401\n  fatal: unable to write request to remote: Broken pipe\n  fatal: could not fetch c4fff0229c9be06ecf576356a4d39a8a755b8d81 from promisor remote\n  warning: Clone succeeded, but checkout failed.\n  You can inspect what was checked out with 'git status'\n  and retry with 'git restore --source=HEAD :/'\n\nFix the immediate problem by including the authorization headers in the\nprobe_rpc() request as well.\n\nSigned-off-by: Aaron Plattner <aplattner@nvidia.com>\nLink: [1] https://github.com/Binary-Eater/git-credential-msal\n---\nIf http_auth.multistage were set in this scenario, then probe_rpc() would have\nreturned HTTP_REAUTH and this would have probably worked by generating a new\nBearer token. And we might need to use HTTP_REAUTH to handle the case where the\ntoken expires between the initial request and this one, but I don't think\ntackling that in this patch makes sense since the original Bearer token was\nstill valid and git just didn't try using it. And setting multistage (the\n'continue' parameter in git-credential(1)) doesn't make sense for Bearer tokens\nsince the token comes from an external agent.\n\n remote-curl.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/remote-curl.c b/remote-curl.c\nindex 69f919454a..1d0ae72521 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -877,6 +877,8 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)\n \theaders = curl_slist_append(headers, rpc->hdr_content_type);\n \theaders = curl_slist_append(headers, rpc->hdr_accept);\n \n+\theaders = http_append_auth_header(&http_auth, headers);\n+\n \tcurl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);\n \tcurl_easy_setopt(slot->curl, CURLOPT_POST, 1L);\n \tcurl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);\n-- \n2.51.2\n\n"},{"id":"532311","messageId":"gn2laka3nl6vb66mvwyqie5ztvwzloadv2xcowzd33bgohecba@duf5klzyuteo","threadId":"64473","inReplyTo":"20251112223722.376330-1-aplattner@nvidia.com","subject":"Re: [PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Lucas De Marchi","fromEmail":"demarchi@kernel.org","sentAt":"2025-12-16T21:50:40Z","receivedAt":"2025-12-16T21:50:43Z","isPatch":true,"sender":{"key":"demarchi@kernel.org","avatar":null},"body":"On Wed, Nov 12, 2025 at 02:37:18PM -0800, Aaron Plattner wrote:\n>If a large request requires post_rpc() to call probe_rpc(), the latter\n>does not use the authorization credentials used for other requests. If\n>this fails with an HTTP 401 error and http_auth.multistage isn't set,\n>then the whole request just fails.\n>\n>For example, using git-credential-msal [1], the following attempt to clone a\n>large repository fails partway through because the initial request to download\n>the commit history and promisor packs succeeds, but the\n>subsequent request to download the blobs needed to construct the working\n>tree fails with a 401 error and the checkout fails.\n>\n>(lines removed for brevity)\n>\n>  git clone --filter=blob:none https://secure-server.example/repo\n>  11:03:26.855369 git.c:502               trace: built-in: git clone --filter=blob:none https://secure-server.example/repo\n>  Cloning into 'sw'...\n>  warning: templates not found in /home/aaron/share/git-core/templates\n>  11:03:26.857169 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>  11:03:27.012104 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>  11:03:27.049243 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>  11:03:27.049270 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>  11:03:27.053786 run-command.c:673       trace: run_command: 'git credential-msal get'\n>  11:03:27.952830 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>  11:03:27.952849 http.c:849              => Send header: Authorization: Bearer <redacted>\n>  11:03:27.995419 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>  11:03:28.230039 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>  11:03:28.230208 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>  11:03:28.230216 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>  11:03:28.230221 http.c:849              => Send header: Authorization: Bearer <redacted>\n>  11:03:28.269085 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>  11:03:28.684163 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>  11:03:28.684379 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>  11:03:28.684391 http.c:849              => Send header: Accept: application/x-git-upload-pack-result\n>  11:03:28.684393 http.c:849              => Send header: Authorization: Bearer <redacted>\n>  11:03:28.869546 run-command.c:673       trace: run_command: git index-pack --stdin --fix-thin '--keep=fetch-pack 43856 on dgx-spark' --promisor\n>  11:06:39.861237 run-command.c:673       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin\n>  11:06:39.865981 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>  11:06:39.868039 run-command.c:673       trace: run_command: git-remote-https origin https://secure-server.example/repo\n>  11:07:30.412575 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>  11:07:30.456285 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>  11:07:30.456318 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>  11:07:30.456439 run-command.c:673       trace: run_command: 'git credential-cache get'\n>  11:07:30.461266 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>  11:07:30.461282 http.c:849              => Send header: Authorization: Bearer <redacted>\n>  11:07:30.501628 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>  11:07:34.725262 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>  11:07:34.725279 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>  11:07:34.761407 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>  11:07:34.761443 http.c:890              == Info: Bearer authentication problem, ignoring.\n>  11:07:34.761453 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>  11:07:34.761509 http.c:890              == Info: The requested URL returned error: 401\n>  11:07:34.761530 http.c:890              == Info: closing connection #0\n>  11:07:34.761913 run-command.c:673       trace: run_command: 'git credential-cache erase'\n>  11:07:34.761927 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-cache erase' 'git credential-cache erase'\n>  11:07:34.768069 git.c:502               trace: built-in: git credential-cache erase\n>  11:07:34.768690 run-command.c:673       trace: run_command: 'git credential-msal erase'\n>  11:07:34.768713 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-msal erase' 'git credential-msal erase'\n>  11:07:34.772742 git.c:808               trace: exec: git-credential-msal erase\n>  11:07:34.772783 run-command.c:673       trace: run_command: git-credential-msal erase\n>  11:07:34.772819 run-command.c:765       trace: start_command: /usr/bin/git-credential-msal erase\n>  error: RPC failed; HTTP 401 curl 22 The requested URL returned error: 401\n>  fatal: unable to write request to remote: Broken pipe\n>  fatal: could not fetch c4fff0229c9be06ecf576356a4d39a8a755b8d81 from promisor remote\n>  warning: Clone succeeded, but checkout failed.\n>  You can inspect what was checked out with 'git status'\n>  and retry with 'git restore --source=HEAD :/'\n>\n>Fix the immediate problem by including the authorization headers in the\n>probe_rpc() request as well.\n>\n>Signed-off-by: Aaron Plattner <aplattner@nvidia.com>\n\nTested-by: Lucas De Marchi <demarchi@kernel.org>\n\nthanks,\nLucas De Marchi\n"},{"id":"533376","messageId":"aWEV2qs8MHqt_JXC@pks.im","threadId":"64473","inReplyTo":"20251112223722.376330-1-aplattner@nvidia.com","subject":"Re: [PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-09T14:51:06Z","receivedAt":"2026-01-09T14:51:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nsorry for taking so long to review your patch, but I didn't really dare\nto review it as I'm not that familiar with the subsystem in question.\nBut given that nobody else reviewed it, either, let me try my best to at\nleast provide _some_ helpful feedback to hopefully move this forward.\n\nOn Wed, Nov 12, 2025 at 02:37:18PM -0800, Aaron Plattner wrote:\n> If a large request requires post_rpc() to call probe_rpc(), the latter\n> does not use the authorization credentials used for other requests. If\n> this fails with an HTTP 401 error and http_auth.multistage isn't set,\n> then the whole request just fails.\n> \n> For example, using git-credential-msal [1], the following attempt to clone a\n> large repository fails partway through because the initial request to download\n> the commit history and promisor packs succeeds, but the\n> subsequent request to download the blobs needed to construct the working\n> tree fails with a 401 error and the checkout fails.\n\nOkay.\n\n> (lines removed for brevity)\n> \n>   git clone --filter=blob:none https://secure-server.example/repo\n>   11:03:26.855369 git.c:502               trace: built-in: git clone --filter=blob:none https://secure-server.example/repo\n>   Cloning into 'sw'...\n>   warning: templates not found in /home/aaron/share/git-core/templates\n>   11:03:26.857169 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>   11:03:27.012104 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>   11:03:27.049243 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>   11:03:27.049270 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>   11:03:27.053786 run-command.c:673       trace: run_command: 'git credential-msal get'\n>   11:03:27.952830 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>   11:03:27.952849 http.c:849              => Send header: Authorization: Bearer <redacted>\n>   11:03:27.995419 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>   11:03:28.230039 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>   11:03:28.230208 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>   11:03:28.230216 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>   11:03:28.230221 http.c:849              => Send header: Authorization: Bearer <redacted>\n>   11:03:28.269085 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>   11:03:28.684163 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>   11:03:28.684379 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>   11:03:28.684391 http.c:849              => Send header: Accept: application/x-git-upload-pack-result\n>   11:03:28.684393 http.c:849              => Send header: Authorization: Bearer <redacted>\n>   11:03:28.869546 run-command.c:673       trace: run_command: git index-pack --stdin --fix-thin '--keep=fetch-pack 43856 on dgx-spark' --promisor\n>   11:06:39.861237 run-command.c:673       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin\n>   11:06:39.865981 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>   11:06:39.868039 run-command.c:673       trace: run_command: git-remote-https origin https://secure-server.example/repo\n>   11:07:30.412575 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>   11:07:30.456285 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>   11:07:30.456318 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>   11:07:30.456439 run-command.c:673       trace: run_command: 'git credential-cache get'\n>   11:07:30.461266 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>   11:07:30.461282 http.c:849              => Send header: Authorization: Bearer <redacted>\n>   11:07:30.501628 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>   11:07:34.725262 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>   11:07:34.725279 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>   11:07:34.761407 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n\nOkay, here we see the 401 error code.\n\n>   11:07:34.761443 http.c:890              == Info: Bearer authentication problem, ignoring.\n>   11:07:34.761453 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>   11:07:34.761509 http.c:890              == Info: The requested URL returned error: 401\n>   11:07:34.761530 http.c:890              == Info: closing connection #0\n>   11:07:34.761913 run-command.c:673       trace: run_command: 'git credential-cache erase'\n>   11:07:34.761927 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-cache erase' 'git credential-cache erase'\n>   11:07:34.768069 git.c:502               trace: built-in: git credential-cache erase\n>   11:07:34.768690 run-command.c:673       trace: run_command: 'git credential-msal erase'\n>   11:07:34.768713 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-msal erase' 'git credential-msal erase'\n>   11:07:34.772742 git.c:808               trace: exec: git-credential-msal erase\n>   11:07:34.772783 run-command.c:673       trace: run_command: git-credential-msal erase\n>   11:07:34.772819 run-command.c:765       trace: start_command: /usr/bin/git-credential-msal erase\n\nAnd as we think that we've already set up authentication, this error\ncode will cause us to think that the credentials that we've got are\ninvalid. Consequently, we invalidate the credentials that we've stored.\nNaturally, this will cause _all_ subsequent requests to fail as we're no\nlonger authenticated at all.\n\n>   error: RPC failed; HTTP 401 curl 22 The requested URL returned error: 401\n>   fatal: unable to write request to remote: Broken pipe\n>   fatal: could not fetch c4fff0229c9be06ecf576356a4d39a8a755b8d81 from promisor remote\n>   warning: Clone succeeded, but checkout failed.\n>   You can inspect what was checked out with 'git status'\n>   and retry with 'git restore --source=HEAD :/'\n> \n> Fix the immediate problem by including the authorization headers in the\n> probe_rpc() request as well.\n> \n> Signed-off-by: Aaron Plattner <aplattner@nvidia.com>\n> Link: [1] https://github.com/Binary-Eater/git-credential-msal\n> ---\n> If http_auth.multistage were set in this scenario, then probe_rpc() would have\n> returned HTTP_REAUTH and this would have probably worked by generating a new\n> Bearer token. And we might need to use HTTP_REAUTH to handle the case where the\n> token expires between the initial request and this one, but I don't think\n> tackling that in this patch makes sense since the original Bearer token was\n> still valid and git just didn't try using it. And setting multistage (the\n> 'continue' parameter in git-credential(1)) doesn't make sense for Bearer tokens\n> since the token comes from an external agent.\n\nThis is something I was wondering about. Specifically, I saw the loop\nthat we had around `HTTP_REAUTH`:\n\n\t\tdo {\n\t\t\terr = probe_rpc(rpc, &results);\n\t\t\tif (err == HTTP_REAUTH)\n\t\t\t\tcredential_fill(the_repository, &http_auth, 0);\n\t\t} while (err == HTTP_REAUTH);\n\nI then double-checked that we indeed get `HTTP_REAUTH` as an error code\non a 401, so I was wondering why this doesn't lead to an infinite loop.\nI didn't connect it with the \"multistage\" thing though.\n\nIn any case, I think this information would be useful to have in the\ncommit message to help guide readers.\n\n>  remote-curl.c | 2 ++\n>  1 file changed, 2 insertions(+)\n> \n> diff --git a/remote-curl.c b/remote-curl.c\n> index 69f919454a..1d0ae72521 100644\n> --- a/remote-curl.c\n> +++ b/remote-curl.c\n> @@ -877,6 +877,8 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)\n>  \theaders = curl_slist_append(headers, rpc->hdr_content_type);\n>  \theaders = curl_slist_append(headers, rpc->hdr_accept);\n>  \n> +\theaders = http_append_auth_header(&http_auth, headers);\n> +\n>  \tcurl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);\n>  \tcurl_easy_setopt(slot->curl, CURLOPT_POST, 1L);\n>  \tcurl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);\n\nThe change looks simple enough, and matches what we do in `post_rpc()`\nitself.\n\nIt would be great to have a test case for this. It might be possible to\nuse t5563-simple-http-auth as an example, where we already know to set\nup an HTTP server with authentication.\n\nThanks!\n\nPatrick\n"},{"id":"533396","messageId":"2e103c5b-8cb3-40ec-aa0e-793f85a1f80d@nvidia.com","threadId":"64473","inReplyTo":"aWEV2qs8MHqt_JXC@pks.im","subject":"Re: [PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Aaron Plattner","fromEmail":"aplattner@nvidia.com","sentAt":"2026-01-09T17:57:30Z","receivedAt":"2026-01-09T17:58:20Z","isPatch":true,"sender":{"key":"aplattner@nvidia.com","avatar":"https://avatars.githubusercontent.com/u/343551?v=4"},"body":"On 1/9/26 6:51 AM, Patrick Steinhardt wrote:\n> Hi,\n> \n> sorry for taking so long to review your patch, but I didn't really dare\n> to review it as I'm not that familiar with the subsystem in question.\n> But given that nobody else reviewed it, either, let me try my best to at\n> least provide _some_ helpful feedback to hopefully move this forward.\n\nThanks Patrick!\n\n> On Wed, Nov 12, 2025 at 02:37:18PM -0800, Aaron Plattner wrote:\n>> If a large request requires post_rpc() to call probe_rpc(), the latter\n>> does not use the authorization credentials used for other requests. If\n>> this fails with an HTTP 401 error and http_auth.multistage isn't set,\n>> then the whole request just fails.\n>>\n>> For example, using git-credential-msal [1], the following attempt to clone a\n>> large repository fails partway through because the initial request to download\n>> the commit history and promisor packs succeeds, but the\n>> subsequent request to download the blobs needed to construct the working\n>> tree fails with a 401 error and the checkout fails.\n> \n> Okay.\n> \n>> (lines removed for brevity)\n>>\n>>    git clone --filter=blob:none https://secure-server.example/repo\n>>    11:03:26.855369 git.c:502               trace: built-in: git clone --filter=blob:none https://secure-server.example/repo\n>>    Cloning into 'sw'...\n>>    warning: templates not found in /home/aaron/share/git-core/templates\n>>    11:03:26.857169 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>>    11:03:27.012104 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>>    11:03:27.049243 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>>    11:03:27.049270 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>>    11:03:27.053786 run-command.c:673       trace: run_command: 'git credential-msal get'\n>>    11:03:27.952830 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>>    11:03:27.952849 http.c:849              => Send header: Authorization: Bearer <redacted>\n>>    11:03:27.995419 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>>    11:03:28.230039 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>>    11:03:28.230208 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>>    11:03:28.230216 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>>    11:03:28.230221 http.c:849              => Send header: Authorization: Bearer <redacted>\n>>    11:03:28.269085 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>>    11:03:28.684163 http.c:890              == Info: Reusing existing https: connection with host secure-server.example\n>>    11:03:28.684379 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>>    11:03:28.684391 http.c:849              => Send header: Accept: application/x-git-upload-pack-result\n>>    11:03:28.684393 http.c:849              => Send header: Authorization: Bearer <redacted>\n>>    11:03:28.869546 run-command.c:673       trace: run_command: git index-pack --stdin --fix-thin '--keep=fetch-pack 43856 on dgx-spark' --promisor\n>>    11:06:39.861237 run-command.c:673       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin\n>>    11:06:39.865981 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo\n>>    11:06:39.868039 run-command.c:673       trace: run_command: git-remote-https origin https://secure-server.example/repo\n>>    11:07:30.412575 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>>    11:07:30.456285 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n>>    11:07:30.456318 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>>    11:07:30.456439 run-command.c:673       trace: run_command: 'git credential-cache get'\n>>    11:07:30.461266 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1\n>>    11:07:30.461282 http.c:849              => Send header: Authorization: Bearer <redacted>\n>>    11:07:30.501628 http.c:849              <= Recv header: HTTP/1.1 200 OK\n>>    11:07:34.725262 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1\n>>    11:07:34.725279 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request\n>>    11:07:34.761407 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized\n> \n> Okay, here we see the 401 error code.\n> \n>>    11:07:34.761443 http.c:890              == Info: Bearer authentication problem, ignoring.\n>>    11:07:34.761453 http.c:849              <= Recv header: WWW-Authenticate: Bearer error=\"invalid_request\", error_description=\"No bearer token found in the request\", msal-tenant-id=\"<tenant>\", msal-client-id=\"<client>\"\n>>    11:07:34.761509 http.c:890              == Info: The requested URL returned error: 401\n>>    11:07:34.761530 http.c:890              == Info: closing connection #0\n>>    11:07:34.761913 run-command.c:673       trace: run_command: 'git credential-cache erase'\n>>    11:07:34.761927 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-cache erase' 'git credential-cache erase'\n>>    11:07:34.768069 git.c:502               trace: built-in: git credential-cache erase\n>>    11:07:34.768690 run-command.c:673       trace: run_command: 'git credential-msal erase'\n>>    11:07:34.768713 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-msal erase' 'git credential-msal erase'\n>>    11:07:34.772742 git.c:808               trace: exec: git-credential-msal erase\n>>    11:07:34.772783 run-command.c:673       trace: run_command: git-credential-msal erase\n>>    11:07:34.772819 run-command.c:765       trace: start_command: /usr/bin/git-credential-msal erase\n> \n> And as we think that we've already set up authentication, this error\n> code will cause us to think that the credentials that we've got are\n> invalid. Consequently, we invalidate the credentials that we've stored.\n> Naturally, this will cause _all_ subsequent requests to fail as we're no\n> longer authenticated at all.\n> \n>>    error: RPC failed; HTTP 401 curl 22 The requested URL returned error: 401\n>>    fatal: unable to write request to remote: Broken pipe\n>>    fatal: could not fetch c4fff0229c9be06ecf576356a4d39a8a755b8d81 from promisor remote\n>>    warning: Clone succeeded, but checkout failed.\n>>    You can inspect what was checked out with 'git status'\n>>    and retry with 'git restore --source=HEAD :/'\n>>\n>> Fix the immediate problem by including the authorization headers in the\n>> probe_rpc() request as well.\n>>\n>> Signed-off-by: Aaron Plattner <aplattner@nvidia.com>\n>> Link: [1] https://github.com/Binary-Eater/git-credential-msal\n>> ---\n>> If http_auth.multistage were set in this scenario, then probe_rpc() would have\n>> returned HTTP_REAUTH and this would have probably worked by generating a new\n>> Bearer token. And we might need to use HTTP_REAUTH to handle the case where the\n>> token expires between the initial request and this one, but I don't think\n>> tackling that in this patch makes sense since the original Bearer token was\n>> still valid and git just didn't try using it. And setting multistage (the\n>> 'continue' parameter in git-credential(1)) doesn't make sense for Bearer tokens\n>> since the token comes from an external agent.\n> \n> This is something I was wondering about. Specifically, I saw the loop\n> that we had around `HTTP_REAUTH`:\n> \n> \t\tdo {\n> \t\t\terr = probe_rpc(rpc, &results);\n> \t\t\tif (err == HTTP_REAUTH)\n> \t\t\t\tcredential_fill(the_repository, &http_auth, 0);\n> \t\t} while (err == HTTP_REAUTH);\n> \n> I then double-checked that we indeed get `HTTP_REAUTH` as an error code\n> on a 401, so I was wondering why this doesn't lead to an infinite loop.\n> I didn't connect it with the \"multistage\" thing though.\n\nRight. I think we don't actually get HTTP_REAUTH because of this logic \nin handle_curl_result:\n\n\telse if (results->http_code == 401) {\n\t\tif ((http_auth.username && http_auth.password) ||\\\n\t\t    (http_auth.authtype && http_auth.credential)) {\n\t\t\tif (http_auth.multistage) {\n\t\t\t\tcredential_clear_secrets(&http_auth);\n\t\t\t\treturn HTTP_REAUTH;\n\t\t\t}\n\t\t\tcredential_reject(the_repository, &http_auth);\n\t\t\tif (always_auth_proactively())\n\t\t\t\thttp_proactive_auth = PROACTIVE_AUTH_NONE;\n\t\t\treturn HTTP_NOAUTH;\n\t\t} else {\n\t\t\thttp_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;\n\t\t\tif (results->auth_avail) {\n\t\t\t\thttp_auth_methods &= results->auth_avail;\n\t\t\t\thttp_auth_methods_restricted = 1;\n\t\t\t}\n\t\t\treturn HTTP_REAUTH;\n\t\t}\n\t}\n\nIn this case, http_auth.authtype and http_auth.credential are set and \nhttp_auth.multistage is false. So it proceeds to call \ncredential_reject() and return with HTTP_NOAUTH which causes the calling \nfunctions to fail immediately.\n\n> In any case, I think this information would be useful to have in the\n> commit message to help guide readers.\n> \n>>   remote-curl.c | 2 ++\n>>   1 file changed, 2 insertions(+)\n>>\n>> diff --git a/remote-curl.c b/remote-curl.c\n>> index 69f919454a..1d0ae72521 100644\n>> --- a/remote-curl.c\n>> +++ b/remote-curl.c\n>> @@ -877,6 +877,8 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)\n>>   \theaders = curl_slist_append(headers, rpc->hdr_content_type);\n>>   \theaders = curl_slist_append(headers, rpc->hdr_accept);\n>>   \n>> +\theaders = http_append_auth_header(&http_auth, headers);\n>> +\n>>   \tcurl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);\n>>   \tcurl_easy_setopt(slot->curl, CURLOPT_POST, 1L);\n>>   \tcurl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);\n> \n> The change looks simple enough, and matches what we do in `post_rpc()`\n> itself.\n> \n> It would be great to have a test case for this. It might be possible to\n> use t5563-simple-http-auth as an example, where we already know to set\n> up an HTTP server with authentication.\n\nI'll look into that. It wasn't obvious to me how to make it hit this RPC \ncase specifically but I'll see if I can figure out a way.\n\n-- Aaron\n\n> \n> Thanks!\n> \n> Patrick\n\n"},{"id":"533400","messageId":"c09387eb-0847-4130-85d1-9da8a3f64164@nvidia.com","threadId":"64473","inReplyTo":"2e103c5b-8cb3-40ec-aa0e-793f85a1f80d@nvidia.com","subject":"Re: [PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Aaron Plattner","fromEmail":"aplattner@nvidia.com","sentAt":"2026-01-09T18:39:10Z","receivedAt":"2026-01-09T18:40:02Z","isPatch":true,"sender":{"key":"aplattner@nvidia.com","avatar":"https://avatars.githubusercontent.com/u/343551?v=4"},"body":"On 1/9/26 9:57 AM, Aaron Plattner wrote:\n> On 1/9/26 6:51 AM, Patrick Steinhardt wrote:\n[...]\n>>> diff --git a/remote-curl.c b/remote-curl.c\n>>> index 69f919454a..1d0ae72521 100644\n>>> --- a/remote-curl.c\n>>> +++ b/remote-curl.c\n>>> @@ -877,6 +877,8 @@ static int probe_rpc(struct rpc_state *rpc, \n>>> struct slot_results *results)\n>>>       headers = curl_slist_append(headers, rpc->hdr_content_type);\n>>>       headers = curl_slist_append(headers, rpc->hdr_accept);\n>>> +    headers = http_append_auth_header(&http_auth, headers);\n>>> +\n>>>       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);\n>>>       curl_easy_setopt(slot->curl, CURLOPT_POST, 1L);\n>>>       curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);\n>>\n>> The change looks simple enough, and matches what we do in `post_rpc()`\n>> itself.\n>>\n>> It would be great to have a test case for this. It might be possible to\n>> use t5563-simple-http-auth as an example, where we already know to set\n>> up an HTTP server with authentication.\n> \n> I'll look into that. It wasn't obvious to me how to make it hit this RPC \n> case specifically but I'll see if I can figure out a way.\n\nI asked AI to try generating a test case for me and it discovered that \nthe problem doesn't reproduce with Basic auth because git sets \nCURLOPT_USERNAME and CURLOPT_PASSWORD and curl implicitly includes those \nin subsequent requests without git having to add them explicitly. If we \nused CURLOPT_XOAUTH2_BEARER like imap-send.c does, then curl would \npresumably do the same thing behind the scenes.\n\nThat said, I'm not sure using that makes sense since the credential \nhelper just tells git to use Bearer auth and what the token is, but not \nwhether it's OAuth2 or some other kind of token. I don't know if that \nmatters. Rahul, do you have any opinions there since you're familiar \nwith this stuff than I am?\n\nAnyway, the test it came up with creates a repository with 2000 branches \nto get the reply to hit the large_request=1 case and then uses a simple \ncredential helper with a dummy Bearer token to trigger the problem. If \nyou think the current fix and that test scenario sound reasonable, I'll \nclean it up and send out a v2.\n\n-- Aaron\n"},{"id":"533564","messageId":"aWSvJbYBWpJc-Vcc@pks.im","threadId":"64473","inReplyTo":"c09387eb-0847-4130-85d1-9da8a3f64164@nvidia.com","subject":"Re: [PATCH] remote-curl: Use auth for probe_rpc() requests too","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-01-12T08:21:57Z","receivedAt":"2026-01-12T08:22:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 09, 2026 at 10:39:10AM -0800, Aaron Plattner wrote:\n> On 1/9/26 9:57 AM, Aaron Plattner wrote:\n> > On 1/9/26 6:51 AM, Patrick Steinhardt wrote:\n> [...]\n> > > > diff --git a/remote-curl.c b/remote-curl.c\n> > > > index 69f919454a..1d0ae72521 100644\n> > > > --- a/remote-curl.c\n> > > > +++ b/remote-curl.c\n> > > > @@ -877,6 +877,8 @@ static int probe_rpc(struct rpc_state *rpc,\n> > > > struct slot_results *results)\n> > > >       headers = curl_slist_append(headers, rpc->hdr_content_type);\n> > > >       headers = curl_slist_append(headers, rpc->hdr_accept);\n> > > > +    headers = http_append_auth_header(&http_auth, headers);\n> > > > +\n> > > >       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L);\n> > > >       curl_easy_setopt(slot->curl, CURLOPT_POST, 1L);\n> > > >       curl_easy_setopt(slot->curl, CURLOPT_URL, rpc->service_url);\n> > > \n> > > The change looks simple enough, and matches what we do in `post_rpc()`\n> > > itself.\n> > > \n> > > It would be great to have a test case for this. It might be possible to\n> > > use t5563-simple-http-auth as an example, where we already know to set\n> > > up an HTTP server with authentication.\n> > \n> > I'll look into that. It wasn't obvious to me how to make it hit this RPC\n> > case specifically but I'll see if I can figure out a way.\n> \n> I asked AI to try generating a test case for me and it discovered that the\n> problem doesn't reproduce with Basic auth because git sets CURLOPT_USERNAME\n> and CURLOPT_PASSWORD and curl implicitly includes those in subsequent\n> requests without git having to add them explicitly. If we used\n> CURLOPT_XOAUTH2_BEARER like imap-send.c does, then curl would presumably do\n> the same thing behind the scenes.\n> \n> That said, I'm not sure using that makes sense since the credential helper\n> just tells git to use Bearer auth and what the token is, but not whether\n> it's OAuth2 or some other kind of token. I don't know if that matters.\n> Rahul, do you have any opinions there since you're familiar with this stuff\n> than I am?\n> \n> Anyway, the test it came up with creates a repository with 2000 branches to\n> get the reply to hit the large_request=1 case and then uses a simple\n> credential helper with a dummy Bearer token to trigger the problem. If you\n> think the current fix and that test scenario sound reasonable, I'll clean it\n> up and send out a v2.\n\nCreating 2000 branches can be done efficiently via a single\ngit-update-ref(1) call, so this wouldn't cause the test to become\nprohibitively expensive. And if that manages to reproduce the problem it\nsounds like a reasonable way forward.\n\nThanks!\n\nPatrick\n"}]}