クローンから origin remote を削除し、SDK がメインリポジトリに戻る経路を遮断

This commit is contained in:
nrslib 2026-01-29 12:50:55 +09:00
parent c84c6166b0
commit 0cdec9afce
6 changed files with 28 additions and 15 deletions

View File

@ -30,7 +30,7 @@ describe('autoCommitAndPush', () => {
return Buffer.from(''); return Buffer.from('');
}); });
const result = autoCommitAndPush('/tmp/clone', 'my-task'); const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.commitHash).toBe('abc1234'); expect(result.commitHash).toBe('abc1234');
@ -50,10 +50,10 @@ describe('autoCommitAndPush', () => {
expect.objectContaining({ cwd: '/tmp/clone' }) expect.objectContaining({ cwd: '/tmp/clone' })
); );
// Verify push was called // Verify push was called with projectDir directly (no origin remote)
expect(mockExecFileSync).toHaveBeenCalledWith( expect(mockExecFileSync).toHaveBeenCalledWith(
'git', 'git',
['push', 'origin', 'HEAD'], ['push', '/project', 'HEAD'],
expect.objectContaining({ cwd: '/tmp/clone' }) expect.objectContaining({ cwd: '/tmp/clone' })
); );
}); });
@ -67,7 +67,7 @@ describe('autoCommitAndPush', () => {
return Buffer.from(''); return Buffer.from('');
}); });
const result = autoCommitAndPush('/tmp/clone', 'my-task'); const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.commitHash).toBeUndefined(); expect(result.commitHash).toBeUndefined();
@ -90,7 +90,7 @@ describe('autoCommitAndPush', () => {
// Verify push was NOT called // Verify push was NOT called
expect(mockExecFileSync).not.toHaveBeenCalledWith( expect(mockExecFileSync).not.toHaveBeenCalledWith(
'git', 'git',
['push', 'origin', 'HEAD'], ['push', '/project', 'HEAD'],
expect.anything() expect.anything()
); );
}); });
@ -100,7 +100,7 @@ describe('autoCommitAndPush', () => {
throw new Error('git error: not a git repository'); throw new Error('git error: not a git repository');
}); });
const result = autoCommitAndPush('/tmp/clone', 'my-task'); const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.commitHash).toBeUndefined(); expect(result.commitHash).toBeUndefined();
@ -120,7 +120,7 @@ describe('autoCommitAndPush', () => {
return Buffer.from(''); return Buffer.from('');
}); });
autoCommitAndPush('/tmp/clone', 'test-task'); autoCommitAndPush('/tmp/clone', 'test-task', '/project');
// Find the commit call // Find the commit call
const commitCall = mockExecFileSync.mock.calls.find( const commitCall = mockExecFileSync.mock.calls.find(
@ -145,7 +145,7 @@ describe('autoCommitAndPush', () => {
return Buffer.from(''); return Buffer.from('');
}); });
autoCommitAndPush('/tmp/clone', '認証機能を追加する'); autoCommitAndPush('/tmp/clone', '認証機能を追加する', '/project');
const commitCall = mockExecFileSync.mock.calls.find( const commitCall = mockExecFileSync.mock.calls.find(
call => (call[1] as string[])[0] === 'commit' call => (call[1] as string[])[0] === 'commit'

View File

@ -228,7 +228,7 @@ program
const taskSuccess = await executeTask(task, execCwd, selectedWorkflow, cwd); const taskSuccess = await executeTask(task, execCwd, selectedWorkflow, cwd);
if (taskSuccess && isWorktree) { if (taskSuccess && isWorktree) {
const commitResult = autoCommitAndPush(execCwd, task); const commitResult = autoCommitAndPush(execCwd, task, cwd);
if (commitResult.success && commitResult.commitHash) { if (commitResult.success && commitResult.commitHash) {
success(`Auto-committed & pushed: ${commitResult.commitHash}`); success(`Auto-committed & pushed: ${commitResult.commitHash}`);
} else if (!commitResult.success) { } else if (!commitResult.success) {

View File

@ -317,7 +317,7 @@ export async function instructBranch(
// 6. Auto-commit+push if successful // 6. Auto-commit+push if successful
if (taskSuccess) { if (taskSuccess) {
const commitResult = autoCommitAndPush(clone.path, item.taskSlug); const commitResult = autoCommitAndPush(clone.path, item.taskSlug, projectDir);
if (commitResult.success && commitResult.commitHash) { if (commitResult.success && commitResult.commitHash) {
info(`Auto-committed & pushed: ${commitResult.commitHash}`); info(`Auto-committed & pushed: ${commitResult.commitHash}`);
} else if (!commitResult.success) { } else if (!commitResult.success) {

View File

@ -81,7 +81,7 @@ export async function executeAndCompleteTask(
const completedAt = new Date().toISOString(); const completedAt = new Date().toISOString();
if (taskSuccess && isWorktree) { if (taskSuccess && isWorktree) {
const commitResult = autoCommitAndPush(execCwd, task.name); const commitResult = autoCommitAndPush(execCwd, task.name, cwd);
if (commitResult.success && commitResult.commitHash) { if (commitResult.success && commitResult.commitHash) {
info(`Auto-committed & pushed: ${commitResult.commitHash}`); info(`Auto-committed & pushed: ${commitResult.commitHash}`);
} else if (!commitResult.success) { } else if (!commitResult.success) {

View File

@ -32,8 +32,9 @@ export interface AutoCommitResult {
* *
* @param cloneCwd - The clone directory * @param cloneCwd - The clone directory
* @param taskName - Task name used in commit message * @param taskName - Task name used in commit message
* @param projectDir - The main project directory (push target)
*/ */
export function autoCommitAndPush(cloneCwd: string, taskName: string): AutoCommitResult { export function autoCommitAndPush(cloneCwd: string, taskName: string, projectDir: string): AutoCommitResult {
log.info('Auto-commit starting', { cwd: cloneCwd, taskName }); log.info('Auto-commit starting', { cwd: cloneCwd, taskName });
try { try {
@ -71,13 +72,13 @@ export function autoCommitAndPush(cloneCwd: string, taskName: string): AutoCommi
log.info('Auto-commit created', { commitHash, message: commitMessage }); log.info('Auto-commit created', { commitHash, message: commitMessage });
// Push to origin so the branch is reflected in the main repo // Push directly to the main repo (origin was removed to isolate the clone)
execFileSync('git', ['push', 'origin', 'HEAD'], { execFileSync('git', ['push', projectDir, 'HEAD'], {
cwd: cloneCwd, cwd: cloneCwd,
stdio: 'pipe', stdio: 'pipe',
}); });
log.info('Pushed to origin'); log.info('Pushed to main repo', { projectDir });
return { return {
success: true, success: true,

View File

@ -137,6 +137,12 @@ export function createSharedClone(projectDir: string, options: WorktreeOptions):
stdio: 'pipe', stdio: 'pipe',
}); });
// Remove origin remote so Claude Code SDK won't follow it back to the main repo
execFileSync('git', ['remote', 'remove', 'origin'], {
cwd: clonePath,
stdio: 'pipe',
});
// Checkout branch // Checkout branch
if (branchExists(clonePath, branch)) { if (branchExists(clonePath, branch)) {
execFileSync('git', ['checkout', branch], { execFileSync('git', ['checkout', branch], {
@ -185,6 +191,12 @@ export function createTempCloneForBranch(projectDir: string, branch: string): Wo
stdio: 'pipe', stdio: 'pipe',
}); });
// Remove origin remote so Claude Code SDK won't follow it back to the main repo
execFileSync('git', ['remote', 'remove', 'origin'], {
cwd: clonePath,
stdio: 'pipe',
});
execFileSync('git', ['checkout', branch], { execFileSync('git', ['checkout', branch], {
cwd: clonePath, cwd: clonePath,
stdio: 'pipe', stdio: 'pipe',