fix: give git-lfs the checkout token once (#1371)

`builtin:checkout` set the token both in its persisted credentials file and in the step's environment. git-lfs before 3.8.0 then sent `Authorization` twice on the LFS batch request, which nginx rejects with 400.

Write the credentials file once the checkout succeeded, since it only serves later steps. A failed checkout no longer persists credentials.

Users can also upgrade git-lfs to fix the root bug, but this fix is worth to keep regardless.

Fixes: https://gitea.com/gitea/runner/issues/1370
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1371
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
silverwindandbircni authored and bircni committed 2026-11-04 06:42:00 +00:00
1 parent b65fd0f8f8
commit 54b2cb944c
2 files changed
+15 -6

No files matched your search

+5 -5
View File
@@ -174,9 +174,6 @@ func Main(ctx context.Context, c *action.Context) (err error) {
env["GIT_SSH_COMMAND"] = shellquote.Join(ssh...)
config += "[core]\n\tsshCommand = " + strconv.Quote(env["GIT_SSH_COMMAND"]) + "\n"
}
if !persist {
config = ""
}
if co.objectFormat == "" || co.sha == "" && !strings.HasPrefix(co.ref, "refs/") {
advertised, err := lsRemote(ctx, c, env, co.url, co.ref)
@@ -191,7 +188,7 @@ func Main(ctx context.Context, c *action.Context) (err error) {
if ref != co.sha {
co.outputRef = cmp.Or(ref, co.ref) // as given, like actions/checkout
}
if err := c.Container.Copy(runnerTemp, &container.FileEntry{Name: credentials, Mode: 0o600, Body: config})(ctx); err != nil {
if err := c.Container.Copy(runnerTemp, &container.FileEntry{Name: credentials, Mode: 0o600})(ctx); err != nil {
return err
}
@@ -215,7 +212,10 @@ func Main(ctx context.Context, c *action.Context) (err error) {
}
commands = commands[fetched:]
}
return clone(commands)
if err := clone(commands); err != nil || !persist {
return err
}
return c.Container.Copy(runnerTemp, &container.FileEntry{Name: credentials, Mode: 0o600, Body: config})(ctx) // for later steps, the step itself authenticates through env
}
func gitEnv(c *action.Context, config []string) map[string]string {
+10 -1
View File
@@ -54,7 +54,7 @@ func TestCheckout(t *testing.T) {
server := t.TempDir()
serverURL := (&url.URL{Scheme: "file", Path: "/" + strings.TrimPrefix(filepath.ToSlash(server), "/")}).String()
var sha string
_, lfsErr := exec.LookPath("git-lfs")
lfsPath, lfsErr := exec.LookPath("git-lfs")
require.True(t, t.Run("fixtures", func(t *testing.T) {
t.Run("history", func(t *testing.T) {
t.Parallel()
@@ -106,6 +106,10 @@ test -s "$2" || exit 1
printf '%s\n' "$@" > "$SSH_ARGS"
for last; do :; done
exec sh -c "$(printf '%s' "$last" | sed "s#git-upload-pack '/\(.*\)\.git'#git upload-pack '$SSH_SERVER/\1'#")"
`), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(tools, "git-lfs"), []byte(`#!/bin/sh
git config --get-regexp extraheader > "$LFS_HEADERS"
exec "$LFS" "$@"
`), 0o755))
globalConfig := filepath.Join(t.TempDir(), "gitconfig")
require.NoError(t, os.WriteFile(globalConfig, []byte("[protocol \"file\"]\n\tallow = always\n"), 0o644))
@@ -214,6 +218,7 @@ exec sh -c "$(printf '%s' "$last" | sed "s#git-upload-pack '/\(.*\)\.git'#git up
Env: map[string]string{
"PATH": tools + string(os.PathListSeparator) + os.Getenv("PATH"), "GIT_CONFIG_GLOBAL": globalConfig, "GIT_CONFIG_NOSYSTEM": "1",
"GIT_DIR": host, "GITHUB_OUTPUT": output, "SSH_SERVER": filepath.ToSlash(server), "SSH_ARGS": filepath.Join(runnerTemp, "ssh-args"),
"LFS": lfsPath, "LFS_HEADERS": filepath.Join(runnerTemp, "lfs-headers"),
},
})
}
@@ -279,6 +284,10 @@ exec sh -c "$(printf '%s' "$last" | sed "s#git-upload-pack '/\(.*\)\.git'#git up
headers, _ := exec.Command("git", "-C", repo, "config", "--get-all", "http."+serverURL+"/.extraheader").Output()
assert.Equal(t, persisted, string(headers) == "\nAuthorization: Basic dG9rZW46dG9r\n", repo)
}
if tc.inputs["lfs"] == "true" && runtime.GOOS != "windows" {
headers, _ := os.ReadFile(filepath.Join(runnerTemp, "lfs-headers"))
assert.Equal(t, 1, strings.Count(string(headers), "Authorization"), string(headers))
}
if tc.inputs["ssh-key"] != "" {
keys, _ := filepath.Glob(filepath.Join(runnerTemp, "git-ssh-key-*"))
require.Len(t, keys, 1)