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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user