--- llm: google/gemini-3.1-pro-preview status: 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//`. 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//`. - **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//`. - 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/` to check if the branch exists. If it does, fail fast with an error indicating the branch collision. - Run `git worktree add -b ` 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 ` and `git branch -D `. 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/ && git worktree prune && git branch -d $(git branch --format='%(refname:short)' --list 'opencode//*')` ## Non-obvious Code Blocks **Path Sanitization & Generation (Example):** ```ts 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):** ```ts 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):** ```ts async function undoWorktree(worktreePath: string, branchName: string): Promise { 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; } ```