From 287a9eb546ca2f748b79bc5aceedea3266cac5b3 Mon Sep 17 00:00:00 2001 From: marcel <186313745+marcelvict@users.noreply.github.com> Date: Wed, 15 Apr 2026 01:15:36 -0400 Subject: [PATCH] fix(repocache): pass explicit env to remote-facing git subprocesses (#1029) fix(repocache): pass explicit env to remote-facing git subprocesses --- server/internal/daemon/repocache/cache.go | 18 ++++++++-- .../internal/daemon/repocache/cache_test.go | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/server/internal/daemon/repocache/cache.go b/server/internal/daemon/repocache/cache.go index 90ac2d7ff..d1c91ce2f 100644 --- a/server/internal/daemon/repocache/cache.go +++ b/server/internal/daemon/repocache/cache.go @@ -14,6 +14,14 @@ import ( "time" ) +// gitEnv returns an environment for git subprocesses that contact remotes. +// It passes the full daemon environment so credential helpers (e.g. gh) can +// locate their config, and disables TTY prompting so auth failures produce +// clear errors instead of blocking on a non-existent terminal. +func gitEnv() []string { + return append(os.Environ(), "GIT_TERMINAL_PROMPT=0") +} + // RepoInfo describes a repository to cache. type RepoInfo struct { URL string @@ -157,6 +165,7 @@ const modernFetchRefspec = "+refs/heads/*:refs/remotes/origin/*" func gitCloneBare(url, dest string) error { cmd := exec.Command("git", "clone", "--bare", url, dest) + cmd.Env = gitEnv() if out, err := cmd.CombinedOutput(); err != nil { // Clean up partial clone. os.RemoveAll(dest) @@ -195,7 +204,9 @@ func gitFetch(barePath string) error { // getRemoteDefaultBranch, but the modern-cache default-branch-change // path (the only path that can't be recovered any other way) relies // on this call. - _ = exec.Command("git", "-C", barePath, "remote", "set-head", "origin", "--auto").Run() + cmd := exec.Command("git", "-C", barePath, "remote", "set-head", "origin", "--auto") + cmd.Env = gitEnv() + _ = cmd.Run() return nil } @@ -203,6 +214,7 @@ func gitFetch(barePath string) error { // gitFetch, which migrates legacy caches first. func runGitFetch(barePath string) error { cmd := exec.Command("git", "-C", barePath, "fetch", "origin") + cmd.Env = gitEnv() if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("git fetch: %s: %w", strings.TrimSpace(string(out)), err) } @@ -235,7 +247,9 @@ func ensureRemoteTrackingLayout(barePath string) error { } // Set refs/remotes/origin/HEAD so getRemoteDefaultBranch can read it. // Non-fatal: if this fails we fall back to origin/main, origin/master. - _ = exec.Command("git", "-C", barePath, "remote", "set-head", "origin", "--auto").Run() + cmd := exec.Command("git", "-C", barePath, "remote", "set-head", "origin", "--auto") + cmd.Env = gitEnv() + _ = cmd.Run() return nil } diff --git a/server/internal/daemon/repocache/cache_test.go b/server/internal/daemon/repocache/cache_test.go index bf0a8ba39..59e09cf6c 100644 --- a/server/internal/daemon/repocache/cache_test.go +++ b/server/internal/daemon/repocache/cache_test.go @@ -13,6 +13,39 @@ func testLogger() *slog.Logger { return slog.Default() } +func TestGitEnv(t *testing.T) { + t.Parallel() + env := gitEnv() + + // Must contain GIT_TERMINAL_PROMPT=0. + found := false + for _, entry := range env { + if entry == "GIT_TERMINAL_PROMPT=0" { + found = true + break + } + } + if !found { + t.Error("gitEnv() must include GIT_TERMINAL_PROMPT=0") + } + + // Must contain HOME from the current environment. + home := os.Getenv("HOME") + if home == "" { + t.Skip("HOME not set in test environment") + } + foundHome := false + for _, entry := range env { + if entry == "HOME="+home { + foundHome = true + break + } + } + if !foundHome { + t.Error("gitEnv() must include HOME from os.Environ()") + } +} + func TestBareDirName(t *testing.T) { t.Parallel() tests := []struct {