authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-26 02:50:06-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-26 02:50:06-07:00
log95030dc674883f0f2a7fd034b32ce742c8cf55d0
tree7aefed9b8ef556a870ddbf3512378f28f0b5e13a
parent295d7cba09bb4b4084482a74b5a42a0cac72a0b1
signature Signed by PGP key B5690EEEBB952194

fix(server): background git status fetches no longer fill the disk with failed repacks (#13812)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

2 files changed, 34 insertions(+), 1 deletions(-)

apps/server/src/vcs/GitVcsDriverCore.test.ts+29
...@@ -1918,6 +1918,35 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -1918,6 +1918,35 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
1918 }),1918 }),
1919 );1919 );
19201920
1921 it.effect("does not start Git auto-maintenance from background upstream fetches", () =>
1922 Effect.gen(function* () {
1923 const cwd = yield* makeTmpDir();
1924 const remote = yield* makeTmpDir("git-vcs-driver-remote-");
1925 const { initialBranch } = yield* initRepoWithCommit(cwd);
1926 yield* git(remote, ["init", "--bare"]);
1927 yield* git(cwd, ["remote", "add", "origin", remote]);
1928 yield* git(cwd, ["push", "-u", "origin", initialBranch]);
1929 yield* git(cwd, ["repack", "-d"]);
1930 yield* writeTextFile(cwd, "second.txt", "second\n");
1931 yield* git(cwd, ["add", "second.txt"]);
1932 yield* git(cwd, ["commit", "-m", "second commit"]);
1933 yield* git(cwd, ["push"]);
1934 yield* git(cwd, ["repack", "-d"]);
1935 // Two packs make `git gc --auto` due, and without detaching it would run inside the fetch.
1936 yield* git(cwd, ["config", "gc.autoPackLimit", "1"]);
1937 yield* git(cwd, ["config", "gc.autoDetach", "false"]);
1938 yield* git(cwd, ["config", "maintenance.autoDetach", "false"]);
1939 const packCount = git(cwd, ["count-objects", "-v"]).pipe(
1940 Effect.map((stdout) => stdout.match(/^packs: (\d+)$/m)?.[1]),
1941 );
1942 assert.equal(yield* packCount, "2");
1943
1944 yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd);
1945
1946 assert.equal(yield* packCount, "2");
1947 }),
1948 );
1949
1921 it.effect("uses origin HEAD for default-branch detection with a non-origin upstream", () =>1950 it.effect("uses origin HEAD for default-branch detection with a non-origin upstream", () =>
1922 Effect.gen(function* () {1951 Effect.gen(function* () {
1923 const cwd = yield* makeTmpDir();1952 const cwd = yield* makeTmpDir();
apps/server/src/vcs/GitVcsDriverCore.ts+5-1
...@@ -1127,10 +1127,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1127,10 +1127,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1127 ): Effect.Effect<void, GitCommandError> => {1127 ): Effect.Effect<void, GitCommandError> => {
1128 const fetchCwd =1128 const fetchCwd =
1129 path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;1129 path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
1130 // `--no-auto-gc` (a synonym of `--no-auto-maintenance` that older Git also knows) keeps
1131 // this poll from starting `git gc --auto`. When that gc fails, for example on a repository
1132 // with missing objects, Git retries it on every fetch and leaves a full-size `tmp_pack_*`
1133 // behind each time, so a background poll could fill the disk.
1130 return executeGit(1134 return executeGit(
1131 "GitVcsDriver.fetchRemoteForStatus",1135 "GitVcsDriver.fetchRemoteForStatus",
1132 fetchCwd,1136 fetchCwd,
1133 ["--git-dir", gitCommonDir, "fetch", "--quiet", "--no-tags", remoteName],1137 ["--git-dir", gitCommonDir, "fetch", "--quiet", "--no-tags", "--no-auto-gc", remoteName],
1134 {1138 {
1135 env: STATUS_UPSTREAM_REFRESH_ENV,1139 env: STATUS_UPSTREAM_REFRESH_ENV,
1136 fallbackErrorDetail: "Background Git fetch exited with a non-zero status.",1140 fallbackErrorDetail: "Background Git fetch exited with a non-zero status.",