fix: prevent shadow git from inheriting global init.templateDir

This commit is contained in:
Roo Code 2026-02-20 15:26:29 +00:00
parent 618aa6652b
commit 1ea52d3141
2 changed files with 54 additions and 2 deletions

View file

@ -39,7 +39,8 @@ function createSanitizedGit(baseDir: string): SimpleGit {
key === "GIT_INDEX_FILE" ||
key === "GIT_OBJECT_DIRECTORY" ||
key === "GIT_ALTERNATE_OBJECT_DIRECTORIES" ||
key === "GIT_CEILING_DIRECTORIES"
key === "GIT_CEILING_DIRECTORIES" ||
key === "GIT_TEMPLATE_DIR"
) {
removedVars.push(`${key}=${value}`)
continue
@ -172,7 +173,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")

View file

@ -824,6 +824,55 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])(
expect(await fs.readFile(testFile, "utf-8")).toBe("Hello, world!")
})
it("does not apply git templates when initializing shadow repo", async () => {
// This test verifies that git init uses --template="" and GIT_TEMPLATE_DIR
// is stripped, preventing system/user git hooks from leaking into the shadow repo.
const templateDir = path.join(tmpDir, `git-template-${Date.now()}`)
const hooksDir = path.join(templateDir, "hooks")
await fs.mkdir(hooksDir, { recursive: true })
await fs.writeFile(path.join(hooksDir, "pre-commit"), "#!/bin/sh\nexit 1", { mode: 0o755 })
const testShadowDir = path.join(tmpDir, `shadow-template-test-${Date.now()}`)
const testWorkspaceDir = path.join(tmpDir, `workspace-template-test-${Date.now()}`)
await initWorkspaceRepo({ workspaceDir: testWorkspaceDir })
const originalTemplateDir = process.env.GIT_TEMPLATE_DIR
process.env.GIT_TEMPLATE_DIR = templateDir
try {
const testService = await klass.create({
taskId: `test-template-${Date.now()}`,
shadowDir: testShadowDir,
workspaceDir: testWorkspaceDir,
log: () => {},
})
await testService.initShadowGit()
// Verify no hooks were copied from the template
const shadowHooksDir = path.join(testShadowDir, ".git", "hooks")
let hookFiles: string[] = []
try {
hookFiles = await fs.readdir(shadowHooksDir)
} catch {
// hooks dir may not exist at all, which is fine
}
// The pre-commit hook from the template should NOT be present
expect(hookFiles).not.toContain("pre-commit")
} finally {
if (originalTemplateDir !== undefined) {
process.env.GIT_TEMPLATE_DIR = originalTemplateDir
} else {
delete process.env.GIT_TEMPLATE_DIR
}
await fs.rm(testShadowDir, { recursive: true, force: true })
await fs.rm(testWorkspaceDir, { recursive: true, force: true })
await fs.rm(templateDir, { recursive: true, force: true })
}
})
it("isolates checkpoint operations from GIT_DIR environment variable", async () => {
// This test verifies the fix for the issue where GIT_DIR environment variable
// causes checkpoint commits to go to the wrong repository.