fix: skip standalone worktree when errand provides cwd

When spawn() receives an explicit cwd (errands), the manager was still
creating a standalone worktree at agent-workdirs/<alias>/ and injecting
its path into the workspace layout prompt. The agent then edited files
in the wrong directory — on a different branch than the errand's.

Now when cwd is provided, we skip worktree creation entirely and use
the caller's cwd for workspace layout, .cw/output/, and all paths.
This commit is contained in:
Lukas May
2026-03-06 22:39:56 +01:00
parent 79a0bd0a74
commit e7c95af1ca

View File

@@ -233,9 +233,13 @@ export class MultiProviderAgentManager implements AgentManager {
log.debug('no accounts available, spawning without account');
}
// 2. Create isolated worktrees
// 2. Create isolated worktrees (skip when caller provides explicit cwd, e.g. errands)
let agentCwd: string;
if (initiativeId) {
if (cwd) {
// Caller manages the worktree (errands). Use their cwd directly.
agentCwd = cwd;
log.info({ alias, agentCwd }, 'using caller-provided cwd, skipping worktree creation');
} else if (initiativeId) {
log.debug({ alias, initiativeId, baseBranch, branchName }, 'creating initiative-based worktrees');
agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch, branchName);
@@ -318,15 +322,13 @@ export class MultiProviderAgentManager implements AgentManager {
// 4. Build spawn command
const { command, args, env: providerEnv } = this.processManager.buildSpawnCommand(provider, prompt);
const finalCwd = cwd ?? agentCwd;
log.info({
agentId,
alias,
command,
args: args.join(' '),
finalCwd,
customCwdProvided: !!cwd,
agentCwd,
providerEnv: Object.keys(providerEnv)
}, 'spawn command built');
@@ -342,7 +344,7 @@ export class MultiProviderAgentManager implements AgentManager {
// 6. Spawn detached subprocess
const { pid, outputFilePath, tailer } = await this.processManager.spawnDetached(
agentId, alias, command, args, cwd ?? agentCwd, processEnv, providerName, prompt,
agentId, alias, command, args, agentCwd, processEnv, providerName, prompt,
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
this.createLogChunkCallback(agentId, alias, 1),
);
@@ -351,7 +353,7 @@ export class MultiProviderAgentManager implements AgentManager {
// Register agent and start polling BEFORE non-critical I/O so that a
// diagnostic-write failure can never orphan a running process.
const activeEntry: ActiveAgent = { agentId, pid, tailer, outputFilePath, agentCwd: finalCwd };
const activeEntry: ActiveAgent = { agentId, pid, tailer, outputFilePath, agentCwd };
this.activeAgents.set(agentId, activeEntry);
// Emit spawned event
@@ -376,19 +378,19 @@ export class MultiProviderAgentManager implements AgentManager {
// Write spawn diagnostic file (non-fatal — .cw/ may not exist yet for
// agents spawned without inputContext, e.g. conflict-resolution agents)
try {
const diagnosticDir = join(finalCwd, '.cw');
const diagnosticDir = join(agentCwd, '.cw');
await mkdir(diagnosticDir, { recursive: true });
const diagnostic = {
timestamp: new Date().toISOString(),
agentId,
alias,
intendedCwd: finalCwd,
intendedCwd: agentCwd,
worktreeId: agent.worktreeId,
provider: providerName,
command,
args,
env: processEnv,
cwdExistsAtSpawn: existsSync(finalCwd),
cwdExistsAtSpawn: existsSync(agentCwd),
initiativeId: initiativeId || null,
customCwdProvided: !!cwd,
accountId: accountId || null,