MCPcopy
hub / github.com/coder/mux / fork

Method fork

src/node/services/workspaceService.ts:6398–6781  ·  view source on GitHub ↗
(
    sourceWorkspaceId: string,
    newName?: string,
    sourceMessageId?: string,
    pendingAutoTitle?: boolean
  )

Source from the content-addressed store, hash-verified

6396 }
6397
6398 async fork(
6399 sourceWorkspaceId: string,
6400 newName?: string,
6401 sourceMessageId?: string,
6402 pendingAutoTitle?: boolean
6403 ): Promise<Result<{ metadata: FrontendWorkspaceMetadata; projectPath: string }>> {
6404 try {
6405 const sourceMetadataResult = await this.aiService.getWorkspaceMetadata(sourceWorkspaceId);
6406 if (!sourceMetadataResult.success) {
6407 return Err(`Failed to get source workspace metadata: ${sourceMetadataResult.error}`);
6408 }
6409 const sourceMetadata = sourceMetadataResult.data;
6410 const partialSnapshot =
6411 sourceMessageId == null ? await this.historyService.readPartial(sourceWorkspaceId) : null;
6412 const foundProjectPath = sourceMetadata.projectPath;
6413 const projectName = sourceMetadata.projectName;
6414 const sourceRuntimeConfig = sourceMetadata.runtimeConfig;
6415
6416 // Policy: do not allow creating new workspaces (including via fork) with a disallowed runtime.
6417 if (this.policyService?.isEnforced()) {
6418 if (!this.policyService.isRuntimeAllowed(sourceRuntimeConfig)) {
6419 return Err("Forking this workspace is not allowed by policy (runtime disabled)");
6420 }
6421 }
6422
6423 // Trust gate: block fork for untrusted projects.
6424 // Same defense-in-depth as create() — the frontend shows a dialog,
6425 // but forking is a secondary creation path that needs backend gating.
6426 const projectConfig = this.config
6427 .loadConfigOrDefault()
6428 .projects.get(stripTrailingSlashes(foundProjectPath));
6429 if (!projectConfig?.trusted) {
6430 return Err(
6431 "This project must be trusted before creating workspaces. Trust the project in Settings → Security, or create a workspace from the project page."
6432 );
6433 }
6434
6435 // Auto-generate branch name (and title) when user omits one (seamless fork).
6436 // Uses pattern: {parentName}-{N} for branch, "{parentTitle} (N)" for title.
6437 const isAutoName = newName == null;
6438 // Fetch all metadata upfront for both branch name and title collision checks.
6439 const allMetadata = isAutoName ? await this.config.getAllWorkspaceMetadata() : [];
6440 let resolvedName: string;
6441 if (isAutoName) {
6442 const existingNamesSet = new Set(
6443 allMetadata.filter((m) => m.projectPath === foundProjectPath).map((m) => m.name)
6444 );
6445 // Also include local branch names to avoid silently reusing stale branches that
6446 // were left behind on disk but no longer exist in config metadata.
6447 try {
6448 for (const branchName of await listLocalBranches(foundProjectPath)) {
6449 existingNamesSet.add(branchName);
6450 }
6451 } catch (error) {
6452 log.debug("Failed to list local branches for fork auto-name preflight", {
6453 projectPath: foundProjectPath,
6454 error: getErrorMessage(error),
6455 });

Callers 7

forkWorkspaceFunction · 0.80
routerFunction · 0.80
handleSlashCommandMethod · 0.80
forkSessionFromWorkspaceFunction · 0.80
fork.test.tsFile · 0.80
handleForkFunction · 0.80

Calls 15

getOrCreateSessionMethod · 0.95
createInitLoggerMethod · 0.95
isExperimentEnabledMethod · 0.95
emitMethod · 0.95
ErrFunction · 0.90
stripTrailingSlashesFunction · 0.90
listLocalBranchesFunction · 0.90
getErrorMessageFunction · 0.90
validateWorkspaceNameFunction · 0.90
createRuntimeFunction · 0.90

Tested by

no test coverage detected