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

View File

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

View File

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

View File

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

View File

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

View File

@ -137,6 +137,12 @@ export function createSharedClone(projectDir: string, options: WorktreeOptions):
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
if (branchExists(clonePath, branch)) {
execFileSync('git', ['checkout', branch], {
@ -185,6 +191,12 @@ export function createTempCloneForBranch(projectDir: string, branch: string): Wo
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], {
cwd: clonePath,
stdio: 'pipe',