From b68022b383c66368b0e6a2d0238f3817c428f361 Mon Sep 17 00:00:00 2001 From: Rusiru Sadathana <27785781+RusiruSadathana@users.noreply.github.com> Date: Thu, 5 Nov 2026 18:16:10 +0530 Subject: [PATCH 1/2] fix(server): prevent worktree creation from failing when Git output exceeds 1 MB --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 80 ++++++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 7 ++ 2 files changed, 87 insertions(+) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 532a6db2b9e9f..e8b07594649f3 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1608,6 +1608,86 @@ it.effect("lets a worktree checkout that is still working run past five minutes" ), ); +it.effect.each([ + { exitCode: 0, reportProgress: true }, + { exitCode: 0, reportProgress: false }, + { exitCode: 1, reportProgress: true }, +])( + "finishes a noisy worktree checkout using its exit status (%j)", + ({ exitCode, reportProgress }) => + Effect.gen(function* () { + const updates: Array<{ percent: number; completed: number; total: number }> = []; + const outputDrained = yield* Deferred.make(); + const noise = `${"x".repeat(1_000_001)}\n`; + const spawner = ChildProcessSpawner.make((command) => { + if (!ChildProcess.isStandardCommand(command) || !command.args.includes("worktree")) { + return Effect.succeed(makeSuccessfulHandle("")); + } + return Effect.succeed( + ChildProcessSpawner.makeHandle({ + ...makeSuccessfulHandle(""), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(exitCode)), + stdout: Stream.encodeText( + Stream.concat( + Stream.make(noise), + Stream.fromEffect( + Deferred.succeed(outputDrained, undefined).pipe(Effect.as("hook finished\n")), + ), + ), + ), + stderr: Stream.encodeText(Stream.make(noise, "Updating files: 100% (10/10), done.\r")), + }), + ); + }); + const driver = yield* makeGitVcsDriverCore().pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + ); + const path = yield* Path.Path; + const cwd = yield* makeTmpDir(); + const worktreePath = path.join(cwd, "worktree"); + + const result = yield* driver + .createWorktree( + { cwd, refName: "main", path: worktreePath }, + reportProgress + ? { + progress: { + onCheckoutProgress: (update) => + Effect.sync(() => { + updates.push(update); + }), + }, + } + : {}, + ) + .pipe(Effect.result); + + assert.isTrue(yield* Deferred.isDone(outputDrained)); + assert.deepStrictEqual( + updates, + reportProgress ? [{ percent: 100, completed: 10, total: 10 }] : [], + ); + if (exitCode === 0) { + assert.isTrue(Result.isSuccess(result)); + if (Result.isSuccess(result)) assert.equal(result.success.worktree.path, worktreePath); + } else { + assert.isTrue(Result.isFailure(result)); + if (Result.isFailure(result)) { + assert.equal(result.failure.exitCode, exitCode); + assert.equal(result.failure.detail, "git worktree add failed"); + assert.equal(result.failure.stdoutLength, 1_000_000); + assert.equal(result.failure.stderrLength, 1_000_000); + } + } + }).pipe( + Effect.provide( + layerServerConfig.pipe( + Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)), + ), + ), + ), +); + it.effect("cancels a slow submodule checkout without reporting best-effort success", () => Effect.gen(function* () { const updateStarted = yield* Deferred.make(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 94404b488b136..50669ff5b44aa 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -192,6 +192,7 @@ interface ExecuteGitOptions { env?: NodeJS.ProcessEnv | undefined; maxOutputBytes?: number | undefined; appendTruncationMarker?: boolean | undefined; + keepLineCallbacksAfterTruncation?: boolean | undefined; progress?: GitVcsDriver.ExecuteGitProgress | undefined; writesSharedConfig?: boolean | undefined; } @@ -1155,6 +1156,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ...(options.appendTruncationMarker !== undefined ? { appendTruncationMarker: options.appendTruncationMarker } : {}), + ...(options.keepLineCallbacksAfterTruncation !== undefined + ? { keepLineCallbacksAfterTruncation: options.keepLineCallbacksAfterTruncation } + : {}), ...(options.progress ? { progress: options.progress } : {}), ...(options.writesSharedConfig ? { writesSharedConfig: true } : {}), }).pipe( @@ -4270,6 +4274,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ["-c", `checkout.workers=${checkoutWorkers}`, ...args], { fallbackErrorDetail: "git worktree add failed", + // Hook output is diagnostic; reaching the capture limit must not abort the checkout. + appendTruncationMarker: true, + keepLineCallbacksAfterTruncation: true, // Large checkouts and post-checkout hooks can legitimately run for many // minutes; a fixed deadline kills them and leaves an orphaned worktree. timeoutMs: null, From c3625c39d3010fd9737933cb1345f3228b6faa45 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 03:24:27 +0000 Subject: [PATCH 2/2] test(server): keep only regression cases for noisy worktree checkout AI-assisted; verified with the tests and tools listed above. --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 31 +++++++------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index d7916d343d093..062ca5980351a 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1659,13 +1659,9 @@ it.effect("lets a worktree checkout that is still working run past five minutes" ), ); -it.effect.each([ - { exitCode: 0, reportProgress: true }, - { exitCode: 0, reportProgress: false }, - { exitCode: 1, reportProgress: true }, -])( +it.effect.each([{ exitCode: 0 }, { exitCode: 1 }])( "finishes a noisy worktree checkout using its exit status (%j)", - ({ exitCode, reportProgress }) => + ({ exitCode }) => Effect.gen(function* () { const updates: Array<{ percent: number; completed: number; total: number }> = []; const outputDrained = yield* Deferred.make(); @@ -1700,24 +1696,19 @@ it.effect.each([ const result = yield* driver .createWorktree( { cwd, refName: "main", path: worktreePath }, - reportProgress - ? { - progress: { - onCheckoutProgress: (update) => - Effect.sync(() => { - updates.push(update); - }), - }, - } - : {}, + { + progress: { + onCheckoutProgress: (update) => + Effect.sync(() => { + updates.push(update); + }), + }, + }, ) .pipe(Effect.result); assert.isTrue(yield* Deferred.isDone(outputDrained)); - assert.deepStrictEqual( - updates, - reportProgress ? [{ percent: 100, completed: 10, total: 10 }] : [], - ); + assert.deepStrictEqual(updates, [{ percent: 100, completed: 10, total: 10 }]); if (exitCode === 0) { assert.isTrue(Result.isSuccess(result)); if (Result.isSuccess(result)) assert.equal(result.success.worktree.path, worktreePath);