Forest

VSCode extension for parallel feature development using git worktrees. One Linear ticket = one branch = one worktree = one VSCode window.
Install from VS Code Marketplace
| Command |
Description |
| New Tree |
Unified wizard: pick new or existing branch, optionally link a Linear ticket |
| Ship |
Push branch + create PR (optionally as draft, with optional automerge) + move ticket to configured status |
| Cleanup |
Auto-triggered when PR is merged: remove worktree + branches + move ticket to configured status |
| Delete Tree |
Interactive form: choose branch cleanup (keep / local only / all), Linear status, and PR handling |
| Update |
Merge from the base branch + re-copy env files |
| List |
Quick-pick list of all active trees |
Prerequisites
git (required)
gh CLI (for PR creation and merge, unless you disable GitHub integration)
Setup
Add .forest/config.json to your repo root (tip: ask Claude to generate one for your project):
{
"version": 1,
"copy": [".env", ".env.local"],
"symlink": ["node_modules"],
"shortcuts": {
"cli": [
{ "name": "install", "command": "bun install --frozen-lockfile", "onNewTree": true },
{ "name": "dev", "command": "bunx turbo dev" },
{ "name": "claude", "command": "claude" },
{ "name": "shell" }
],
"web": [
{ "name": "App", "url": "http://localhost:3000" }
]
},
"linear": {
"teams": ["ENG"],
"showTodos": true,
"statuses": {
"issueList": ["triage", "backlog", "unstarted", "started"],
"onNew": "started",
"onShip": "in review",
"onCleanup": "completed"
}
}
}
Per-developer overrides go in .forest/local.json (should be gitignored):
{
"linear": {
"apiKey": "lin_api_YOUR_KEY"
},
"browser": ["external", "Firefox"],
"terminal": ["iTerm", "integrated"]
}
Generating Config with AI
To set up Forest, ask Claude (or any AI) to read this README and generate .forest/config.json. The AI should inspect the repo and ask you:
- Files to copy into trees? → check which of
.env, .env.local, .envrc exist
- Shortcuts? → what terminals to open (dev server, claude, shell), any browser URLs, any one-time setup commands (e.g.
bun install with onNewTree: true)
- Linear integration? → yes/no, and team key(s) (e.g.
["ENG", "UX"]). Get your API key from Linear account security
Config reference
| Field |
Required |
Default |
Description |
version |
yes |
— |
Always 1 |
copy |
no |
[] |
Files to copy from repo root into each tree |
symlink |
no |
[] |
Directories to symlink from the main repo into each tree (e.g. ["node_modules"]). Symlinks are relative, created after copy, and cleaned up before worktree removal. |
shortcuts |
no |
{cli:[],web:[]} |
Terminals (cli) and browsers (web) to open per tree. Each array holds objects with name and type-specific fields. |
linear |
no |
disabled |
Linear integration. Auto-enabled when teams or apiKey is set. teams is an array of team keys (e.g. ["ENG"] or ["ENG", "UX"]). issueList must use Linear's built-in lowercase state types (triage, backlog, unstarted, started, completed, canceled). onNew, onShip, onCleanup, and onCancel can be state names or types and are matched case-insensitively. Set showTodos: true to surface a Todos sidebar listing your assigned issues in issueList states. |
github |
no |
true |
GitHub integration toggle. Set false to disable |
branchFormat |
no |
${ticketId}-${slug} |
Branch naming for Linear-linked trees. Supports ${ticketId}, ${slug} |
branchNamePrefix |
no |
"" |
Prefill for manual new branches. Supports {YYMMDD}, {YY}, {MM}, {DD} |
baseBranch |
no |
main |
Base branch name (origin/ prefix is added automatically) |
maxTrees |
no |
10 |
Max concurrent worktrees |
tetra |
no |
— |
AI integration via Tetra. Presence opts in to AI features — even "tetra": {} enables it. port (default 24100) is the Tetra HTTP port. commands.commit and commands.pr map Forest actions to Tetra prompt filenames in ~/.config/tetra/commands/; both default to AI Generate commit message and AI Generate PR description. See AI Integration below. |
browser |
no |
["integrated"] |
Browser app list. First item is the default; right-click a shortcut to pick another. Values: integrated (VS Code integrated browser), external (system default), or an app name (e.g. "Firefox") |
terminal |
no |
["integrated"] |
Terminal app list. First item is the default; right-click to pick another. Values: integrated (VS Code terminal), or an external app (iTerm, Terminal, Ghostty). External terminals receive the shortcut command automatically |
Shortcuts support onNewTree: true to auto-open when a tree is first created (e.g. for dependency installation). Terminal shortcuts also accept command and env. Browser shortcuts accept a per-shortcut browser override (same values as the top-level browser setting). Terminal and browser shortcuts show an external-open button on hover. The terminal button opens the system Terminal app; the browser button opens the system default browser. Shortcut values are treated literally: Forest does not expand ${...} placeholders inside shortcut commands, URLs, or env vars.
local.json (gitignored) merges over config.json — use for per-dev overrides.
Features
Tree Grouping
- Deleting — cleanup in progress (loading spinner)
- In progress — no PR created yet
- In review — PR is open
- Done — PR has been merged
- Closed — PR has been closed
Tree Card Indicators
On the active tree card, Forest surfaces:
Pull N for commits behind the remote branch
Main N for commits behind the configured base branch
+N / -N / ~N for working tree changes
PR#N when a pull request exists
Auto-Cleanup on Merged PRs
When a PR is merged, Forest shows a cleanup notification. Choosing Cleanup removes the worktree, deletes the branch, updates the Linear ticket if enabled, and closes the tree window when appropriate.
Update / Rebase
Update merges your tree from the configured base branch and re-copies config files. Rebase does the same with git rebase instead of merge. If either operation fails, Forest shows an error and leaves conflicts for you to resolve manually.
Direnv Support
If a .envrc file exists in the tree, Forest automatically runs direnv allow during tree creation and when switching into that tree.
Remote-SSH
Forest runs as a workspace extension (extensionKind: ["workspace", "ui"]), so over a VS Code Remote-SSH connection it installs on the remote host and operates the repo there — git, gh, terminals, and .forest/config.json all resolve on the remote. Local use is unchanged.
AI Integration
Forest uses Tetra for AI-generated commit messages and PR descriptions. To enable:
- Install Tetra (a macOS menu bar app)
- Add Tetra commands to
~/.config/tetra/commands/:
AI Generate commit message.prompt.md — generates commit messages
AI Generate PR description.prompt.md — generates PR descriptions
- Set
"tetra": {} in your .forest/config.json (or local.json) to opt in with defaults. To customize, e.g. {"tetra": {"port": 5000, "commands": {"commit": "My commit prompt", "pr": "My PR prompt"}}}
Example Tetra command (~/.config/tetra/commands/AI Generate PR description.prompt.md):
---
llm: cerebras_qwen
temperature: 0.2
---
Write a concise pull request description for the following diff. Use markdown. Start with a short summary paragraph, then a bullet list of key changes if needed. Do not include a title. Keep the total response under 500 words.
PR title: {{title}}
Diff:
{{text}}
OUTPUT ONLY THE PR DESCRIPTION.
Claude Code Trust
Claude Code asks for trust confirmation when opening a new workspace. Since each tree creates a new directory, you'd get this prompt for every tree. To avoid it, add ~/.forest/trees to Claude's trusted directories:
In ~/.claude/settings.json:
{
"trustedDirectories": ["/Users/you/.forest/trees"]
}
Replace /Users/you with your actual home directory.
Enable linear.showTodos: true to show a Todos panel listing Linear issues assigned to you in any of the configured issueList states. Click a todo (or use the inline + button) to start a new tree from that issue — Forest creates the branch, worktree, and window, and moves the ticket to onNew.
Todos refresh automatically every 3 minutes, on window focus, and via the refresh button in the panel header.
Configurable Linear Statuses
Customize which Linear states to show in the Todos sidebar and which states to set on new/ship/cleanup.
Status names in issueList use Linear's built-in types: triage, backlog, unstarted, started, completed, canceled. Status names in onShip, onNew, etc. can be custom workflow state names (e.g. "in review") — Forest resolves them via the Linear API. Use team keys (e.g. ENG), not display names. Multiple teams are supported.
"linear": {
"teams": ["ENG"],
"statuses": {
"issueList": ["backlog", "unstarted"],
"onNew": "started",
"onShip": "in review",
"onCleanup": "completed",
"onCancel": "canceled"
}
}
Usage
Most user-facing actions are available from the Forest sidebar (tree icon in activity bar) or the command palette (Cmd+Shift+P → "Forest: ...").
Typical workflow:
- New Tree from a Linear ticket, or create a new Linear issue in the form
- A new VS Code window opens, then any configured
onNewTree shortcuts run
- Code, test, iterate — each tree is fully isolated
- Ship when ready — pushes and creates a PR (with AI-generated description if enabled)
- Cleanup after merge — removes worktree, branch, and ticket
- Delete to remove a tree (keep branches, delete local only, or delete all)
Switch between trees from the sidebar. All processes keep running in background windows.
Commands
| Command |
Description |
Forest: New Tree |
Create tree (unified wizard) |
Forest: Start |
Create tree from a Linear issue (used by Todos panel) |
Forest: Attach Linear Ticket… |
Link an existing branch to a Linear issue |
Forest: Switch Tree |
Open another tree's window |
Forest: Ship |
Push + create PR (optionally as draft; offers automerge if repo supports it) |
Forest: Ship + Automerge |
Push + create PR + enable automerge when supported |
Forest: Delete Tree |
Interactive deletion with branch/ticket/PR options |
Forest: Update |
Merge from the base branch + re-copy config files |
Forest: Rebase |
Rebase onto the base branch |
Forest: Pull |
Pull latest changes |
Forest: Push |
Push to remote |
Forest: List |
List all trees |
Forest: Run Shortcut… |
Quick-pick any configured terminal/browser shortcut |
Forest: Open Main |
Open main repo window |
Forest: View Pull Request |
Open PR in browser |
Forest: View Linear Ticket |
Open Linear ticket in browser |
Forest: Reveal in Finder |
Open worktree directory in Finder |
Forest: Copy Branch Name |
Copy current tree's branch to clipboard |
Forest: Copy Setup Prompt |
Copy AI setup prompt to clipboard |
Recommended VS Code Settings
"window.nativeTabs": true,
"window.titleBarStyle": "native",
"window.customTitleBarVisibility": "never",
"window.title": "${rootName}", // readable tab labels per worktree
"window.restoreWindows": "preserve", // survives restarts
"window.closeWhenEmpty": true,
"git.openRepositoryInParentFolders": "always", // git works in worktree subdirs
"terminal.integrated.gpuAcceleration": "canvas" // avoids a bug with heavy parallel terminal output
To open terminals as editor tabs (instead of the bottom panel), add this keybinding (Cmd+T):
{
"key": "cmd+t",
"command": "workbench.action.createTerminalEditor"
}
To use Shift+Enter for new lines inside terminal, add this keybinding:
{
"key": "shift+enter",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "\u001b[13;2u"
},
"when": "terminalFocus"
}
Install locally
git clone <repo> && cd forest
bun install
bun run package
npx @vscode/vsce package
code --install-extension vladstudio-forest-*.vsix
Development
bun install
# Press F5 in VSCode to launch Extension Development Host