{"record":{"id":"cf83dbf22269b146","repo":"ruvnet/ruflo","slug":"refusing-to-prepare-writing-worktrees-from-a-dirty","errorCode":null,"errorMessage":"refusing to prepare writing worktrees from a dirty repository","messagePattern":"refusing to prepare writing worktrees from a dirty repository","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/worktrees/coordinator.ts","lineNumber":63,"sourceCode":"  }\n\n  prepare(\n    runId: string,\n    agents: Array<{ id: string; readOnly?: boolean }>,\n    options: { allowDirty?: boolean; baseRef?: string } = {},\n  ): WorktreeRunRecord {\n    assertId(runId, 'run id');\n    if (!agents.length) throw new Error('at least one agent is required');\n    const unique = new Set<string>();\n    for (const agent of agents) {\n      assertId(agent.id, 'agent id');\n      if (unique.has(agent.id)) throw new Error(`duplicate agent id: ${agent.id}`);\n      unique.add(agent.id);\n    }\n    const registryPath = this.registryPath(runId);\n    if (existsSync(registryPath)) return this.status(runId);\n    if (!options.allowDirty && git(this.repoRoot, ['status', '--porcelain']).length > 0) {\n      throw new Error('refusing to prepare writing worktrees from a dirty repository');\n    }\n\n    mkdirSync(this.registryDir, { recursive: true });\n    mkdirSync(join(this.worktreeBase, runId), { recursive: true });\n    const assignments: WorktreeAssignment[] = [];\n    try {\n      for (const agent of agents) {\n        const readOnly = agent.readOnly === true;\n        const branch = `ruflo/${runId}/${agent.id}`;\n        const worktreePath = join(this.worktreeBase, runId, agent.id);\n        if (readOnly) {\n          git(this.repoRoot, ['worktree', 'add', '--detach', worktreePath, options.baseRef ?? 'HEAD']);\n        } else {\n          git(this.repoRoot, ['worktree', 'add', '-b', branch, worktreePath, options.baseRef ?? 'HEAD']);\n        }\n        assignments.push({ agentId: agent.id, branch: readOnly ? '' : branch, path: worktreePath, readOnly });\n      }\n      const record: WorktreeRunRecord = {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/worktrees/coordinator.ts#L45-L81","documentation":"prepare() refuses to create writing worktrees when `git status --porcelain` in repoRoot reports anything and options.allowDirty is not true. Writing worktrees branch from committed HEAD, so uncommitted state would silently diverge between the main checkout and agent worktrees; the coordinator fails fast instead of proceeding from an inexact source state.","triggerScenarios":"prepare(runId, agents) with default options while the repository has staged, modified, or untracked files. A single untracked file that is not gitignored is enough to trigger it.","commonSituations":"Local development with leftover edits; a previous agent wrote into the main checkout instead of its worktree; CI jobs that generate files (dist/, coverage/, logs) without gitignore entries.","solutions":["Commit or stash the changes (git stash, or git add + git commit) so `git status --porcelain` is empty, then re-run prepare()","Add .gitignore entries for generated artifacts and clean untracked files (preview with `git clean -nd`)","If a dirty base is acceptable, call prepare(runId, agents, { allowDirty: true }) — agent worktrees will then branch from HEAD without the uncommitted changes"],"exampleFix":"// before\ncoordinator.prepare('run-1', agents); // throws: dirty repository\n\n// after\nexecSync('git stash --include-untracked', { cwd: repoRoot });\ncoordinator.prepare('run-1', agents);","handlingStrategy":"validation","validationCode":"import { execFileSync } from 'node:child_process';\nfunction isClean(repoRoot: string): boolean {\n  return execFileSync('git', ['-C', repoRoot, 'status', '--porcelain'], { encoding: 'utf8' }).trim().length === 0;\n}\nif (!isClean(repoRoot) && !allowDirty) {\n  throw new Error('repo is dirty — commit, stash, or pass allowDirty');\n}\ncoordinator.prepare(runId, agents, { allowDirty });","typeGuard":null,"tryCatchPattern":"try {\n  coordinator.prepare(runId, agents);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('dirty repository')) {\n    // stash and retry once, or surface to the operator\n  } else throw e;\n}","preventionTips":["Run prepare() only from a clean checkout; commit agent work before starting the next run","Gitignore generated artifacts so untracked files do not count as dirty"],"tags":["git","worktrees","dirty-working-tree","precondition"],"backgroundTag":"dirty-working-tree","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}