mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
fix(loop): make Docker sandbox opt-in and preserve progress file
- Add --sandbox flag to loop command (default: use plain claude -p) - Append to progress.txt instead of overwriting between runs - Display execution mode (Docker sandbox vs Claude CLI) in output
This commit is contained in:
10
.changeset/loop-sandbox-optional.md
Normal file
10
.changeset/loop-sandbox-optional.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
"@tm/core": patch
|
||||||
|
"@tm/cli": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Make Docker sandbox mode opt-in for loop command
|
||||||
|
|
||||||
|
- Add `--sandbox` flag to `task-master loop` (default: use plain `claude -p`)
|
||||||
|
- Preserve progress.txt between runs (append instead of overwrite)
|
||||||
|
- Display execution mode in loop startup output
|
||||||
@@ -22,6 +22,7 @@ export interface LoopCommandOptions {
|
|||||||
progressFile?: string;
|
progressFile?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
project?: string;
|
project?: string;
|
||||||
|
sandbox?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoopCommand extends Command {
|
export class LoopCommand extends Command {
|
||||||
@@ -47,6 +48,7 @@ export class LoopCommand extends Command {
|
|||||||
'--project <path>',
|
'--project <path>',
|
||||||
'Project root directory (auto-detected if not provided)'
|
'Project root directory (auto-detected if not provided)'
|
||||||
)
|
)
|
||||||
|
.option('--sandbox', 'Run Claude in Docker sandbox mode')
|
||||||
.action((options: LoopCommandOptions) => this.execute(options));
|
.action((options: LoopCommandOptions) => this.execute(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +82,17 @@ export class LoopCommand extends Command {
|
|||||||
storageType: this.tmCore.tasks.getStorageType()
|
storageType: this.tmCore.tasks.getStorageType()
|
||||||
});
|
});
|
||||||
|
|
||||||
this.handleSandboxAuth();
|
// Only check sandbox auth when --sandbox flag is used
|
||||||
|
if (options.sandbox) {
|
||||||
|
this.handleSandboxAuth();
|
||||||
|
}
|
||||||
|
|
||||||
console.log(chalk.cyan('Starting Task Master Loop...'));
|
console.log(chalk.cyan('Starting Task Master Loop...'));
|
||||||
console.log(chalk.dim(`Preset: ${prompt}`));
|
console.log(chalk.dim(`Preset: ${prompt}`));
|
||||||
console.log(chalk.dim(`Max iterations: ${iterations}`));
|
console.log(chalk.dim(`Max iterations: ${iterations}`));
|
||||||
|
console.log(
|
||||||
|
chalk.dim(`Mode: ${options.sandbox ? 'Docker sandbox' : 'Claude CLI'}`)
|
||||||
|
);
|
||||||
|
|
||||||
// Show next task only for default preset (other presets don't use Task Master tasks)
|
// Show next task only for default preset (other presets don't use Task Master tasks)
|
||||||
if (prompt === 'default') {
|
if (prompt === 'default') {
|
||||||
@@ -105,7 +113,8 @@ export class LoopCommand extends Command {
|
|||||||
iterations,
|
iterations,
|
||||||
prompt,
|
prompt,
|
||||||
progressFile,
|
progressFile,
|
||||||
tag: options.tag
|
tag: options.tag,
|
||||||
|
sandbox: options.sandbox
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.tmCore.loop.run(config);
|
const result = await this.tmCore.loop.run(config);
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -67,6 +67,7 @@
|
|||||||
"ollama-ai-provider-v2": "^1.3.1",
|
"ollama-ai-provider-v2": "^1.3.1",
|
||||||
"open": "^10.2.0",
|
"open": "^10.2.0",
|
||||||
"ora": "^8.2.0",
|
"ora": "^8.2.0",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
"simple-git": "^3.28.0",
|
"simple-git": "^3.28.0",
|
||||||
"steno": "^4.0.2",
|
"steno": "^4.0.2",
|
||||||
"terminal-link": "^5.0.0",
|
"terminal-link": "^5.0.0",
|
||||||
|
|||||||
@@ -117,7 +117,8 @@
|
|||||||
"turndown": "^7.2.2",
|
"turndown": "^7.2.2",
|
||||||
"undici": "^7.16.0",
|
"undici": "^7.16.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12",
|
||||||
|
"proper-lockfile": "^4.1.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@anthropic-ai/claude-code": "^2.0.59",
|
"@anthropic-ai/claude-code": "^2.0.59",
|
||||||
|
|||||||
@@ -188,7 +188,8 @@ export class LoopDomain {
|
|||||||
partial.progressFile ??
|
partial.progressFile ??
|
||||||
path.join(this.projectRoot, '.taskmaster', 'progress.txt'),
|
path.join(this.projectRoot, '.taskmaster', 'progress.txt'),
|
||||||
sleepSeconds: partial.sleepSeconds ?? 5,
|
sleepSeconds: partial.sleepSeconds ?? 5,
|
||||||
tag: partial.tag
|
tag: partial.tag,
|
||||||
|
sandbox: partial.sandbox ?? false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { PRESETS, isPreset as checkIsPreset } from '../presets/index.js';
|
import { PRESETS, isPreset as checkIsPreset } from '../presets/index.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -80,7 +80,11 @@ export class LoopService {
|
|||||||
console.log(`━━━ Iteration ${i} of ${config.iterations} ━━━`);
|
console.log(`━━━ Iteration ${i} of ${config.iterations} ━━━`);
|
||||||
|
|
||||||
const prompt = await this.buildPrompt(config, i);
|
const prompt = await this.buildPrompt(config, i);
|
||||||
const iteration = this.executeIteration(prompt, i);
|
const iteration = this.executeIteration(
|
||||||
|
prompt,
|
||||||
|
i,
|
||||||
|
config.sandbox ?? false
|
||||||
|
);
|
||||||
iterations.push(iteration);
|
iterations.push(iteration);
|
||||||
|
|
||||||
// Check for early exit conditions
|
// Check for early exit conditions
|
||||||
@@ -135,9 +139,11 @@ export class LoopService {
|
|||||||
private async initProgressFile(config: LoopConfig): Promise<void> {
|
private async initProgressFile(config: LoopConfig): Promise<void> {
|
||||||
await mkdir(path.dirname(config.progressFile), { recursive: true });
|
await mkdir(path.dirname(config.progressFile), { recursive: true });
|
||||||
const tagLine = config.tag ? `# Tag: ${config.tag}\n` : '';
|
const tagLine = config.tag ? `# Tag: ${config.tag}\n` : '';
|
||||||
await writeFile(
|
// Append to existing progress file instead of overwriting
|
||||||
|
await appendFile(
|
||||||
config.progressFile,
|
config.progressFile,
|
||||||
`# Task Master Loop Progress
|
`
|
||||||
|
# Task Master Loop Progress
|
||||||
# Started: ${new Date().toISOString()}
|
# Started: ${new Date().toISOString()}
|
||||||
# Preset: ${config.prompt}
|
# Preset: ${config.prompt}
|
||||||
# Max Iterations: ${config.iterations}
|
# Max Iterations: ${config.iterations}
|
||||||
@@ -217,20 +223,23 @@ Loop iteration ${iteration} of ${config.iterations}${tagInfo}`;
|
|||||||
|
|
||||||
private executeIteration(
|
private executeIteration(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
iterationNum: number
|
iterationNum: number,
|
||||||
|
sandbox: boolean
|
||||||
): LoopIteration {
|
): LoopIteration {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
const result = spawnSync(
|
// Use docker sandbox or plain claude based on config
|
||||||
'docker',
|
const command = sandbox ? 'docker' : 'claude';
|
||||||
['sandbox', 'run', 'claude', '-p', prompt],
|
const args = sandbox
|
||||||
{
|
? ['sandbox', 'run', 'claude', '-p', prompt]
|
||||||
cwd: this.projectRoot,
|
: ['-p', prompt, '--allowedTools', 'Edit,Write,Bash,Read,Glob,Grep'];
|
||||||
encoding: 'utf-8',
|
|
||||||
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
|
const result = spawnSync(command, args, {
|
||||||
stdio: ['inherit', 'pipe', 'pipe']
|
cwd: this.projectRoot,
|
||||||
}
|
encoding: 'utf-8',
|
||||||
);
|
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
|
||||||
|
stdio: ['inherit', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
|
||||||
const output = (result.stdout || '') + (result.stderr || '');
|
const output = (result.stdout || '') + (result.stderr || '');
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface LoopConfig {
|
|||||||
sleepSeconds: number;
|
sleepSeconds: number;
|
||||||
/** Tag context to operate on (optional) */
|
/** Tag context to operate on (optional) */
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
/** Run Claude in Docker sandbox mode (default: false) */
|
||||||
|
sandbox?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user