Files
2026-03-12 21:08:18 +05:30

4.0 KiB

llm, status
llm status
google/gemini-3.1-pro-preview draft

Multi-model git worktree support plan

Goal

Enhance the multi-model tool to:

  1. Create a unique git worktree for each session + modelName.
  2. Store worktree folders inside ~/.local/share/opencode/multi-model/<sessionName>/<windowName>.
  3. Initialize opencode with that specific worktree to avoid conflicts.

Implementation Details

  • Target file: .opencode/tools/multi-model.ts
  • Path Sanitization:
    • Sanitize sessionName (e.g., replace non-alphanumeric with hyphens) to ensure safe paths and branch names. windowName is already sanitized by existing code.
  • Worktree Path Construction:
    • worktreePath: ~/.local/share/opencode/multi-model/<sanitizedSessionName>/<windowName>.
  • Git Repository Check:
    • Run git rev-parse --is-inside-work-tree at the start of execute.
    • If false, gracefully fail with an error multi-model requires a git repository.
  • Creating Worktrees:
    • Iterate through windowPlans to create tmux windows:
      • Generate worktreePath.
      • Generate branchName as opencode/<sanitizedSessionName>/<windowName>.
      • Handle edge cases (Fail Fast approach):
        • Run fs.existsSync(worktreePath) to check if the path exists. If it does, fail fast with an error indicating the path collision.
        • Run git show-ref --verify --quiet refs/heads/<branchName> to check if the branch exists. If it does, fail fast with an error indicating the branch collision.
      • Run git worktree add -b <branchName> <worktreePath> to create the new branch and worktree.
    • In tmux new-session and tmux new-window, pass the newly created worktreePath to the -c argument instead of context.directory.
  • Cleanup Strategy:
    • On launch failure: If tmux or opencode launch fails after worktree creation, run git worktree remove -f <worktreePath> and git branch -D <branchName>. Keep track of successful and failed cleanups (undos) and report them in the tool output if the undo process fails.
    • On success: Output a command snippet to the user on how to clean up the session's worktrees and branches when done: rm -rf ~/.local/share/opencode/multi-model/<sanitizedSessionName> && git worktree prune && git branch -d $(git branch --format='%(refname:short)' --list 'opencode/<sanitizedSessionName>/*')

Non-obvious Code Blocks

Path Sanitization & Generation (Example):

import * as os from "node:os";
import * as path from "node:path";
import * as fs from "node:fs";

function sanitizeName(name: string): string {
  return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
}

function getWorktreePath(safeSession: string, windowName: string): string {
  return path.join(os.homedir(), ".local/share/opencode/multi-model", safeSession, windowName);
}

Creating Worktree (Example):

const safeSession = sanitizeName(sessionName);
const branchName = `opencode/${safeSession}/${plan.windowName}`;

if (fs.existsSync(worktreePath)) {
  return `Error: Worktree path already exists at ${worktreePath}. Clean it up first.`;
}

const branchExists = await runCommand(["git", "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
if (branchExists.ok) {
  return `Error: Branch '${branchName}' already exists.`;
}

const worktreeCmd = ["git", "worktree", "add", "-b", branchName, worktreePath];
const result = await runCommand(worktreeCmd);

if (!result.ok) {
  return `Error: Failed to create worktree for ${plan.model}: ${result.stderr}`;
}

Cleanup on Failure (Example):

async function undoWorktree(worktreePath: string, branchName: string): Promise<string[]> {
  const errors: string[] = [];
  const removeRes = await runCommand(["git", "worktree", "remove", "-f", worktreePath]);
  if (!removeRes.ok) errors.push(`Failed to remove worktree ${worktreePath}: ${removeRes.stderr}`);
  
  const branchRes = await runCommand(["git", "branch", "-D", branchName]);
  if (!branchRes.ok) errors.push(`Failed to delete branch ${branchName}: ${branchRes.stderr}`);
  return errors;
}