From 02dab4eb117e422d1a377311533f7720d92c5efb Mon Sep 17 00:00:00 2001 From: Mnigos Date: Wed, 9 Sep 2026 16:06:27 +0200 Subject: [PATCH] fix(server): let the start-from-origin fetch outlive the 30s git timeout --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 47 ++++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 3 ++ 2 files changed, 50 insertions(+) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index fb741b28ee7c..31a27814115b 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1892,6 +1892,53 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + it.effect("allows remote fetches to run longer than the default command timeout", () => + Effect.gen(function* () { + const delegate = yield* ChildProcessSpawner.ChildProcessSpawner; + const fetchStarted = yield* Deferred.make(); + const delayedFetchSpawner = ChildProcessSpawner.make((command) => + Effect.gen(function* () { + if (ChildProcess.isStandardCommand(command) && command.args[0] === "fetch") { + yield* Deferred.succeed(fetchStarted, undefined); + yield* Effect.sleep("31 seconds"); + } + return yield* delegate.spawn(command); + }), + ); + const driver = yield* makeGitVcsDriverCore().pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, delayedFetchSpawner), + Effect.provide(ServerConfigLayer), + ); + const cwd = yield* makeTmpDir(); + const remote = yield* makeTmpDir("git-remote-"); + const peer = yield* makeTmpDir("git-peer-"); + const { initialBranch } = yield* initRepoWithCommit(cwd); + yield* git(remote, ["init", "--bare"]); + yield* git(cwd, ["remote", "add", "origin", remote]); + yield* git(cwd, ["push", "-u", "origin", initialBranch]); + yield* git(peer, ["clone", remote, "."]); + yield* git(peer, ["config", "user.email", "test@test.com"]); + yield* git(peer, ["config", "user.name", "Test"]); + yield* writeTextFile(peer, "remote-change.txt", "remote\n"); + yield* git(peer, ["add", "remote-change.txt"]); + yield* git(peer, ["commit", "-m", "remote change"]); + yield* git(peer, ["push", "origin", initialBranch]); + const remoteHead = yield* git(peer, ["rev-parse", "HEAD"]); + + const fetching = yield* driver + .fetchRemote({ cwd, remoteName: "origin" }) + .pipe(Effect.forkChild({ startImmediately: true })); + yield* Deferred.await(fetchStarted); + yield* TestClock.adjust("31 seconds"); + yield* Fiber.join(fetching); + + assert.equal( + yield* git(cwd, ["rev-parse", `refs/remotes/origin/${initialBranch}`]), + remoteHead, + ); + }), + ); + it.effect("allows pushes to run longer than the default command timeout", () => Effect.gen(function* () { const delegate = yield* ChildProcessSpawner.ChildProcessSpawner; diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 3c7e018ddea7..8716a1f832a0 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -3039,12 +3039,15 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* const fetchRemote: GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"] = Effect.fn("fetchRemote")( function* (input) { + // A catch-up fetch of a large remote can run for minutes; the default + // 30s deadline kills it mid-pack and every retry starts over, like push. yield* executeGit( "GitVcsDriver.fetchRemote", input.cwd, ["fetch", "--quiet", input.remoteName], { env: STATUS_UPSTREAM_REFRESH_ENV, + timeoutMs: null, fallbackErrorDetail: `git fetch ${input.remoteName} failed`, }, );