fix: Fail fast when agent worktree creation or branch setup fails

Previously, branch computation errors and ensureBranch failures were
silently swallowed for all tasks, allowing execution agents to spawn
without proper git isolation. This caused alert-pony to commit directly
to main instead of its task branch.

- manager.ts: Verify each project worktree subdirectory exists after
  createProjectWorktrees; throw if any are missing. Convert passive
  cwdVerified log to a hard guard.
- dispatch/manager.ts: Make branch computation and ensureBranch errors
  fatal for execution tasks (execute, verify, merge, review) while
  keeping them non-fatal for planning tasks.
This commit is contained in:
Lukas May
2026-03-06 14:08:59 +01:00
parent b419981924
commit 0f1c578269
4 changed files with 27 additions and 7 deletions

View File

@@ -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');

View File

@@ -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');
}
}
}

View File

@@ -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/<alias>/`
3. `AgentProcessManager.createProjectWorktrees()` — creates git worktrees at `agent-workdirs/<alias>/<project>/`. 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

View File

@@ -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/<id>.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