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');
|
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;
|
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');
|
log.debug({ alias, initiativeId, baseBranch, branchName }, 'creating initiative-based worktrees');
|
||||||
agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch, branchName);
|
agentCwd = await this.processManager.createProjectWorktrees(alias, initiativeId, baseBranch, branchName);
|
||||||
|
|
||||||
@@ -318,15 +322,13 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
|
|
||||||
// 4. Build spawn command
|
// 4. Build spawn command
|
||||||
const { command, args, env: providerEnv } = this.processManager.buildSpawnCommand(provider, prompt);
|
const { command, args, env: providerEnv } = this.processManager.buildSpawnCommand(provider, prompt);
|
||||||
const finalCwd = cwd ?? agentCwd;
|
|
||||||
|
|
||||||
log.info({
|
log.info({
|
||||||
agentId,
|
agentId,
|
||||||
alias,
|
alias,
|
||||||
command,
|
command,
|
||||||
args: args.join(' '),
|
args: args.join(' '),
|
||||||
finalCwd,
|
agentCwd,
|
||||||
customCwdProvided: !!cwd,
|
|
||||||
providerEnv: Object.keys(providerEnv)
|
providerEnv: Object.keys(providerEnv)
|
||||||
}, 'spawn command built');
|
}, 'spawn command built');
|
||||||
|
|
||||||
@@ -342,7 +344,7 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
|
|
||||||
// 6. Spawn detached subprocess
|
// 6. Spawn detached subprocess
|
||||||
const { pid, outputFilePath, tailer } = await this.processManager.spawnDetached(
|
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)),
|
(event) => this.outputHandler.handleStreamEvent(agentId, event, this.activeAgents.get(agentId)),
|
||||||
this.createLogChunkCallback(agentId, alias, 1),
|
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
|
// Register agent and start polling BEFORE non-critical I/O so that a
|
||||||
// diagnostic-write failure can never orphan a running process.
|
// 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);
|
this.activeAgents.set(agentId, activeEntry);
|
||||||
|
|
||||||
// Emit spawned event
|
// Emit spawned event
|
||||||
@@ -376,19 +378,19 @@ export class MultiProviderAgentManager implements AgentManager {
|
|||||||
// Write spawn diagnostic file (non-fatal — .cw/ may not exist yet for
|
// Write spawn diagnostic file (non-fatal — .cw/ may not exist yet for
|
||||||
// agents spawned without inputContext, e.g. conflict-resolution agents)
|
// agents spawned without inputContext, e.g. conflict-resolution agents)
|
||||||
try {
|
try {
|
||||||
const diagnosticDir = join(finalCwd, '.cw');
|
const diagnosticDir = join(agentCwd, '.cw');
|
||||||
await mkdir(diagnosticDir, { recursive: true });
|
await mkdir(diagnosticDir, { recursive: true });
|
||||||
const diagnostic = {
|
const diagnostic = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
agentId,
|
agentId,
|
||||||
alias,
|
alias,
|
||||||
intendedCwd: finalCwd,
|
intendedCwd: agentCwd,
|
||||||
worktreeId: agent.worktreeId,
|
worktreeId: agent.worktreeId,
|
||||||
provider: providerName,
|
provider: providerName,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
env: processEnv,
|
env: processEnv,
|
||||||
cwdExistsAtSpawn: existsSync(finalCwd),
|
cwdExistsAtSpawn: existsSync(agentCwd),
|
||||||
initiativeId: initiativeId || null,
|
initiativeId: initiativeId || null,
|
||||||
customCwdProvided: !!cwd,
|
customCwdProvided: !!cwd,
|
||||||
accountId: accountId || null,
|
accountId: accountId || null,
|
||||||
|
|||||||
Reference in New Issue
Block a user