diff --git a/apps/server/agent/manager.ts b/apps/server/agent/manager.ts index 152ac3c..0d650de 100644 --- a/apps/server/agent/manager.ts +++ b/apps/server/agent/manager.ts @@ -239,8 +239,18 @@ export class MultiProviderAgentManager implements AgentManager { log.debug({ alias, initiativeId, baseBranch, branchName }, 'creating initiative-based worktrees'); agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch, branchName); - // Log projects linked to the initiative + // Verify each project worktree subdirectory actually exists const projects = await this.projectRepository.findProjectsByInitiativeId(initiativeId); + for (const project of projects) { + const projectWorktreePath = join(agentCwd, project.name); + if (!existsSync(projectWorktreePath)) { + throw new Error( + `Worktree subdirectory missing after createProjectWorktrees: ${projectWorktreePath}. ` + + `Agent ${alias} cannot run without an isolated worktree.` + ); + } + } + log.info({ alias, initiativeId, @@ -255,11 +265,12 @@ export class MultiProviderAgentManager implements AgentManager { } // Verify the final agentCwd exists - const cwdVerified = existsSync(agentCwd); + if (!existsSync(agentCwd)) { + throw new Error(`Agent workdir does not exist after creation: ${agentCwd}`); + } log.info({ alias, agentCwd, - cwdVerified, initiativeBasedAgent: !!initiativeId }, 'agent workdir setup completed'); diff --git a/apps/server/dispatch/manager.ts b/apps/server/dispatch/manager.ts index 4ef2f35..554dff3 100644 --- a/apps/server/dispatch/manager.ts +++ b/apps/server/dispatch/manager.ts @@ -327,8 +327,13 @@ export class DefaultDispatchManager implements DispatchManager { } } } - } catch { - // Non-fatal: fall back to default branching + } catch (err) { + if (!isPlanningCategory(task.category)) { + // Execution tasks MUST have correct branches — fail loudly + throw new Error(`Failed to compute branches for execution task ${task.id}: ${err}`); + } + // Planning tasks: non-fatal, fall back to default branching + log.debug({ taskId: task.id, err }, 'branch computation skipped for planning task'); } // Ensure branches exist in project clones before spawning worktrees @@ -350,7 +355,10 @@ export class DefaultDispatchManager implements DispatchManager { } } } catch (err) { - log.warn({ taskId: task.id, err }, 'failed to ensure branches for task dispatch'); + if (!isPlanningCategory(task.category)) { + throw new Error(`Failed to ensure branches for execution task ${task.id}: ${err}`); + } + log.warn({ taskId: task.id, err }, 'failed to ensure branches for planning task dispatch'); } } } diff --git a/docs/agent.md b/docs/agent.md index 38f55db..15503bf 100644 --- a/docs/agent.md +++ b/docs/agent.md @@ -32,7 +32,7 @@ 1. **tRPC procedure** calls `agentManager.spawn(options)` 2. Manager generates alias (adjective-animal), creates DB record. Appends inter-agent communication and preview instructions unless `skipPromptExtras: true` (used by conflict-resolution agents to keep prompts lean). -3. `AgentProcessManager.createWorktree()` — creates git worktree at `.cw-worktrees/agent//` +3. `AgentProcessManager.createProjectWorktrees()` — creates git worktrees at `agent-workdirs///`. After creation, each project subdirectory is verified to exist; missing worktrees throw immediately to prevent agents running in the wrong directory. 4. `file-io.writeInputFiles()` — writes `.cw/input/` with assignment files (initiative, pages, phase, task) and read-only context dirs (`context/phases/`, `context/tasks/`) 5. Provider config builds spawn command via `buildSpawnCommand()` 6. `spawnDetached()` — launches detached child process with file output redirection diff --git a/docs/dispatch-events.md b/docs/dispatch-events.md index d9e336d..7f0ffd6 100644 --- a/docs/dispatch-events.md +++ b/docs/dispatch-events.md @@ -69,6 +69,7 @@ InitiativeChangesRequestedEvent { initiativeId, phaseId, taskId } 7. **Summary propagation** — `completeTask()` reads the completing agent's `result.message` and stores it on the task's `summary` column. Dependent tasks see this summary in `context/tasks/.md` frontmatter. 8. **Spawn failure** — If `agentManager.spawn()` throws, the task is blocked via `blockTask()` with the error message. The dispatch cycle continues instead of crashing. 9. **Retry blocked** — `retryBlockedTask(taskId)` resets a blocked task to pending and re-queues it. Exposed via tRPC `retryBlockedTask` mutation. The UI shows a Retry button in the task slide-over when status is `blocked`. +10. **Branch validation** — Branch computation and `ensureBranch` errors are fatal for execution tasks (execute, verify, merge, review) but non-fatal for planning tasks. This prevents agents from spawning without proper branch isolation. ### DispatchManager Methods