diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md index 76733f330..92c5629a4 100644 --- a/get-shit-done/workflows/execute-phase.md +++ b/get-shit-done/workflows/execute-phase.md @@ -307,6 +307,22 @@ Execute each selected wave in sequence. Within a wave: parallel if `PARALLELIZAT EXPECTED_BASE=$(git rev-parse HEAD) ``` + **Sequential dispatch for parallel execution (waves with 2+ agents):** + When spawning multiple agents in a wave, dispatch each `Task()` call **one at a time + with `run_in_background: true`** — do NOT send all Task calls in a single message. + `git worktree add` acquires an exclusive lock on `.git/config.lock`, so simultaneous + calls race for this lock and fail. Sequential dispatch ensures each worktree finishes + creation before the next begins (the round-trip latency of each tool call provides + natural spacing), while all agents still **run in parallel** once created. + + ``` + # CORRECT: dispatch one Task() per message, each with run_in_background: true + # → worktrees created sequentially, agents execute in parallel + # + # WRONG: multiple Task() calls in a single message + # → simultaneous git worktree add → .git/config.lock contention → failures + ``` + ``` Task( subagent_type="gsd-executor", diff --git a/tests/worktree-stagger.test.cjs b/tests/worktree-stagger.test.cjs new file mode 100644 index 000000000..423cebb74 --- /dev/null +++ b/tests/worktree-stagger.test.cjs @@ -0,0 +1,49 @@ +/** + * GSD Worktree Sequential Dispatch Tests + * + * Validates that execute-phase workflow includes sequential dispatch + * instructions to prevent git config.lock contention when multiple + * agents create worktrees in parallel within the same wave. + * + * See: https://github.com/gsd-build/get-shit-done/issues/1511 + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows'); + +describe('worktree sequential dispatch', () => { + const executePhasePath = path.join(WORKFLOWS_DIR, 'execute-phase.md'); + let content; + + test('execute-phase.md exists', () => { + assert.ok(fs.existsSync(executePhasePath), 'execute-phase.md should exist'); + }); + + test('execute-phase explains git config.lock contention', () => { + content = fs.readFileSync(executePhasePath, 'utf-8'); + assert.ok( + content.includes('config.lock'), + 'execute-phase.md should explain the git config.lock race condition' + ); + }); + + test('execute-phase requires sequential dispatch with run_in_background', () => { + content = content || fs.readFileSync(executePhasePath, 'utf-8'); + assert.ok( + content.includes('run_in_background'), + 'execute-phase.md should instruct one-at-a-time dispatch with run_in_background' + ); + }); + + test('execute-phase warns against multiple Task calls in single message', () => { + content = content || fs.readFileSync(executePhasePath, 'utf-8'); + assert.ok( + content.includes('WRONG') && content.includes('single message'), + 'execute-phase.md should warn against sending multiple Task() calls simultaneously' + ); + }); +});