Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/node/runtime/SSHRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1190,21 +1190,29 @@ export class SSHRuntime extends RemoteRuntime {
};
}

// Clone the repo from the source workspace on the remote host.
// NOTE: This intentionally does not attempt to copy uncommitted changes.
initLogger.logStep("Cloning workspace on remote...");
const cloneResult = await execBuffered(
// Copy the source workspace on the remote host so we preserve working tree state.
// Avoid preserving ownership to prevent fork failures when files are owned by another user.
initLogger.logStep("Copying workspace on remote...");
const copyResult = await execBuffered(
this,
`git clone --quiet ${sourceWorkspacePathArg} ${newWorkspacePathArg}`,
`cp -R -P ${sourceWorkspacePathArg} ${newWorkspacePathArg}`,
{
Comment thread
ammar-agent marked this conversation as resolved.
cwd: "/tmp",
timeout: 300,
}
);
if (cloneResult.exitCode !== 0) {
if (copyResult.exitCode !== 0) {
try {
await execBuffered(this, `rm -rf ${newWorkspacePathArg}`, {
cwd: "/tmp",
timeout: 30,
});
} catch {
// Best-effort cleanup of partially copied workspace.
}
return {
success: false,
error: `Failed to clone workspace: ${cloneResult.stderr || cloneResult.stdout}`,
error: `Failed to copy workspace: ${copyResult.stderr || copyResult.stdout}`,
};
}

Expand Down
18 changes: 13 additions & 5 deletions src/node/services/workspaceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1584,11 +1584,8 @@ export class WorkspaceService extends EventEmitter {
}
}

// Block fork for remote runtimes - creates broken workspaces
// Sub-agent task spawning uses a different code path (TaskService.create)
if (isSSHRuntime(sourceRuntimeConfig)) {
return Err("Forking SSH workspaces is not supported. Create a new workspace instead.");
}
// Block fork for Docker runtimes - creates broken workspaces.
// Sub-agent task spawning uses a different code path (TaskService.create).
if (isDockerRuntime(sourceRuntimeConfig)) {
return Err("Forking Docker workspaces is not supported. Create a new workspace instead.");
}
Expand Down Expand Up @@ -1700,6 +1697,17 @@ export class WorkspaceService extends EventEmitter {
forkResult
);

if (forkResult.sourceRuntimeConfig) {
const allMetadataUpdated = await this.config.getAllWorkspaceMetadata();
const updatedMetadata = allMetadataUpdated.find((m) => m.id === sourceWorkspaceId) ?? null;
const sourceSession = this.sessions.get(sourceWorkspaceId);
if (sourceSession) {
sourceSession.emitMetadata(updatedMetadata);
} else {
this.emit("metadata", { workspaceId: sourceWorkspaceId, metadata: updatedMetadata });
}
}

// Compute namedWorkspacePath for frontend metadata
const namedWorkspacePath = runtime.getWorkspacePath(foundProjectPath, newName);

Expand Down
Loading
Loading