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
Concepts
| Term |
Action |
| New Tree |
Unified wizard: pick new or existing branch, optionally link a Linear ticket |
| Ship |
Push branch + create PR (with optional automerge) + move ticket to configured status |
| Cleanup |
Merge PR + delete worktree + move ticket to configured status |
| Delete Tree |
Remove worktree, keep branches |
| Delete Tree + Local |
Remove worktree + local branch, keep remote |
| Delete Tree + Branches |
Remove worktree + all branches + move ticket to canceled |
| Update |
Merge from main + re-copy env files |
| List |
Quick-pick list of all active trees |
Prerequisites
git (required)
gh CLI (for PR creation and merge)
Setup
Add .forest/config.json to your repo root (tip: ask Claude to generate one for your project):
{
"version": 1,
"copy": [".env", ".env.local"],
"shortcuts": [
{ "name": "install", "command": "bun install --frozen-lockfile", "onNewTree": true },
{ "name": "dev", "command": "bunx turbo dev" },
{ "name": "claude", "command": "claude" },
{ "name": "shell" },
{ "name": "App", "url": "http://localhost:3000" }
],
"linear": {
"teams": ["ENG"],
"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"
},
"ai": {
"provider": "gemini",
"model": "gemini-2.5-flash",
"apiKey": "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 https://linear.app/settings/account/security
Config reference
| Field |
Required |
Default |
Description |
version |
yes |
— |
Always 1 |
copy |
no |
[] |
Files to copy from repo root into each tree |
shortcuts |
no |
[] |
Terminals, browsers, files to open per tree |
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"]). statuses controls issue list and lifecycle transitions including onCancel (must use lowercase state names: triage, backlog, unstarted, started, completed, canceled) |
github |
no |
true |
GitHub integration toggle. Set false to disable |
branchFormat |
no |
${ticketId}-${slug} |
Branch naming. Supports ${ticketId}, ${slug} |
baseBranch |
no |
main |
Base branch name (origin/ prefix is added automatically) |
maxTrees |
no |
10 |
Max concurrent worktrees |
ai |
no |
disabled |
AI-generated PR descriptions. Set provider (anthropic, openai, gemini), model, and apiKey. Best placed in local.json |
logging |
no |
true |
File-based logging to ~/.forest/forest.log. Rotates at 5 MB |
browser |
no |
["simple"] |
Browser app list. First item is the default; right-click a shortcut to pick another. Values: simple (VS Code Simple 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 |
Shortcut types are inferred from fields: url → browser, path → file, otherwise → terminal (explicit type still accepted). Shortcuts support onNewTree: true to auto-open when a tree is first created (e.g. for dependency installation). Terminals also accept command and env. Browser shortcuts accept a per-shortcut browser override (same values as the top-level browser setting). Both browser and terminal accept a single string for backward compatibility.
Variable expansion in shortcuts: ${ticketId}, ${branch}, ${slug}, ${repo}, ${treePath}, ${prNumber}, ${prUrl}.
local.json (gitignored) merges over config.json — use for per-dev AI keys and overrides.
Features
Tree Grouping
- Todo — Linear issues without a branch yet
- Cleaning up — cleanup in progress (loading spinner)
- In Progress — no PR created yet
- In Review — PR is open
- Done — PR has been merged
Tree Health Indicators
ENG-1234 Fix login bug 3↓ · 2h
ENG-5678 Add dark mode PR approved · 1d
- N↓ — commits behind base branch
- Age — time since last commit
- PR status — open, approved, changes requested
Auto-Cleanup on Merged PRs
When a PR is merged, you get a notification: "ENG-1234 PR was merged. Clean up?" → click Cleanup to remove the worktree automatically.
Update (Rebase + Refresh)
Update fetches and merges (or rebases) your tree on the base branch and re-copies config files. If the merge/rebase fails, it shows an error.
Shortcut Variable Expansion
Shortcuts support these variables in commands, URLs, and file paths:
| Variable |
Description |
Example value |
${ticketId} |
Linear ticket ID |
ENG-123 |
${branch} |
Full branch name |
ENG-123-fix-login |
${slug} |
Branch name without ticket prefix |
fix-login |
${repo} |
Repository name |
my-app |
${treePath} |
Absolute path to the worktree |
/Users/you/.forest/trees/my-app/ENG-123 |
${prNumber} |
PR number (after ship) |
42 |
${prUrl} |
PR URL (after ship) |
https://github.com/org/repo/pull/42 |
{ "name": "Linear", "url": "https://linear.app/team/issue/${ticketId}" },
{ "name": "PR", "url": "${prUrl}" },
{ "name": "logs", "command": "tail -f ${treePath}/logs/dev.log" }
Direnv Support
If a .envrc file exists in the tree, Forest automatically runs direnv allow during tree creation.
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.
Configurable Linear Statuses
Customize which Linear states to show in the issues 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
All commands 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 New Linear Issue + Tree to create a new ticket)
- A new VSCode window opens with terminals running
- Code, test, iterate — each tree is fully isolated
- Ship when ready — pushes and creates a PR
- 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: Switch Tree |
Open another tree's window |
Forest: Ship |
Push + create PR |
Forest: Ship + Automerge |
Push + create PR + enable automerge |
Forest: Cleanup |
Merge PR + remove tree |
Forest: Delete Tree |
Remove tree, keep branches |
Forest: Delete Tree + Local |
Remove tree + local branch |
Forest: Delete Tree + Branches |
Remove tree + all branches |
Forest: Update |
Merge from main + re-copy config files |
Forest: Rebase |
Rebase onto main |
Forest: List |
List all trees |
Forest: Open Main |
Open main repo window |
Forest: Open PR |
Open PR 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
To open terminals as editor tabs (instead of the bottom panel), add this keybinding (Cmd+T):
{
"key": "cmd+t",
"command": "workbench.action.createTerminalEditor"
}
Install locally
git clone <repo> && cd forest
bun install
bun run package
npx @vscode/vsce package
code --install-extension forest-0.1.0.vsix
Development
bun install
# Press F5 in VSCode to launch Extension Development Host