diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts index bc842c9f18..3d5fc4ff4e 100644 --- a/src/core/checkpoints/index.ts +++ b/src/core/checkpoints/index.ts @@ -18,7 +18,11 @@ import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../../se export async function getCheckpointService( task: Task, - { interval = 250, timeout = 15_000 }: { interval?: number; timeout?: number } = {}, + { + interval = 250, + timeout = 15_000, + isInitialCall = false, + }: { interval?: number; timeout?: number; isInitialCall?: boolean } = {}, ) { if (!task.enableCheckpoints) { return undefined @@ -67,13 +71,12 @@ export async function getCheckpointService( } if (task.checkpointServiceInitializing) { - await pWaitFor( - () => { - console.log("[Task#getCheckpointService] waiting for service to initialize") - return !!task.checkpointService && !!task?.checkpointService?.isInitialized - }, - { interval, timeout }, - ) + // For initial calls, don't apply timeout to allow large repositories to initialize + const waitOptions = isInitialCall ? { interval } : { interval, timeout } + await pWaitFor(() => { + console.log("[Task#getCheckpointService] waiting for service to initialize") + return !!task.checkpointService && !!task?.checkpointService?.isInitialized + }, waitOptions) if (!task?.checkpointService) { task.enableCheckpoints = false return undefined diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index c5be865731..8ea07b26e4 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1661,7 +1661,8 @@ export class Task extends EventEmitter implements TaskLike { private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise { // Kicks off the checkpoints initialization process in the background. - getCheckpointService(this) + // Pass isInitialCall=true to allow unlimited time for large repositories + getCheckpointService(this, { isInitialCall: true }) let nextUserContent = userContent let includeFileDetails = true diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index ba56b8abc6..5b136f5123 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -105,7 +105,21 @@ export abstract class ShadowCheckpointService extends EventEmitter { await git.addConfig("user.name", "Roo Code") await git.addConfig("user.email", "noreply@example.com") await this.writeExcludeFile() - await this.stageAll(git) + + // For initial commit, stage files but with a timeout to prevent hanging on large repos + // Use a promise race to ensure we don't wait forever + const stagePromise = this.stageAll(git) + const timeoutPromise = new Promise((resolve) => { + setTimeout(() => { + this.log( + `[${this.constructor.name}#initShadowGit] Initial staging timed out after 5 seconds, proceeding with empty commit`, + ) + resolve() + }, 5000) // 5 second timeout for initial staging + }) + + await Promise.race([stagePromise, timeoutPromise]) + const { commit } = await git.commit("initial commit", { "--allow-empty": null }) this.baseHash = commit created = true @@ -145,11 +159,14 @@ export abstract class ShadowCheckpointService extends EventEmitter { private async stageAll(git: SimpleGit) { try { - await git.add(".") + // Add --ignore-errors flag to handle permission issues gracefully + // This prevents the operation from failing on files with permission issues + await git.add([".", "--ignore-errors"]) } catch (error) { this.log( `[${this.constructor.name}#stageAll] failed to add files to git: ${error instanceof Error ? error.message : String(error)}`, ) + // Don't throw - allow the operation to continue with whatever files were successfully staged } }