diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index e68a7cfea103..43a60a72f67e 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -106,7 +106,9 @@ export abstract class ShadowCheckpointService extends EventEmitter { this.baseHash = await git.revparse(["HEAD"]) } else { this.log(`[${this.constructor.name}#initShadowGit] creating shadow git repo at ${this.checkpointsDir}`) - await git.init() + // Use --template= to prevent inheriting user's global init.templateDir configuration + // This ensures no unwanted hooks or templates are copied to the shadow repository + await git.init(["--template="]) await git.addConfig("core.worktree", this.workspaceDir) // Sets the working tree to the current workspace. await git.addConfig("commit.gpgSign", "false") // Disable commit signing for shadow repo. await git.addConfig("user.name", "Roo Code") diff --git a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts index 622a90f39abb..8c0597906b68 100644 --- a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts +++ b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts @@ -483,6 +483,46 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])( await fs.rm(shadowDir, { recursive: true, force: true }) await fs.rm(workspaceDir, { recursive: true, force: true }) }) + + it("does not inherit global init.templateDir when creating shadow git repo", async () => { + // Create a new temporary workspace and service for this test + const shadowDir = path.join(tmpDir, `${prefix}-no-template-${Date.now()}`) + const workspaceDir = path.join(tmpDir, `workspace-no-template-${Date.now()}`) + + // Create a workspace without any git repo + await fs.mkdir(workspaceDir, { recursive: true }) + const testFile = path.join(workspaceDir, "test.txt") + await fs.writeFile(testFile, "Test content") + + // Create the service and initialize shadow git + const service = new klass(taskId, shadowDir, workspaceDir, () => {}) + await service.initShadowGit() + + // Verify the shadow git repo was created + const gitDir = path.join(shadowDir, ".git") + expect(await fileExistsAtPath(gitDir)).toBe(true) + + // Verify no hooks directory was created (which would happen if templates were used) + // The --template= flag should prevent any template from being used + const hooksDir = path.join(gitDir, "hooks") + + // Check if hooks directory exists + const hooksExists = await fileExistsAtPath(hooksDir) + + if (hooksExists) { + // If hooks directory exists, it should only contain sample hooks (*.sample files) + // and no executable hooks + const hookFiles = await fs.readdir(hooksDir) + const executableHooks = hookFiles.filter((file) => !file.endsWith(".sample")) + + // There should be no executable hooks from templates + expect(executableHooks).toHaveLength(0) + } + + // Clean up + await fs.rm(shadowDir, { recursive: true, force: true }) + await fs.rm(workspaceDir, { recursive: true, force: true }) + }) }) describe(`${klass.name}#events`, () => {