> ## Documentation Index
> Fetch the complete documentation index at: https://portfolio.subinb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fine, I'll use git worktrees

> Building wtadd and wtrm: two small shell functions that turned git worktree from a feature I knew about into one I actually use.

I’d known about Git worktrees for years, but I never really had a reason to use them. A few weeks ago, I finally gave them a try. Now I’m wondering why I waited so long.

## What is a worktree?

A [worktree](https://git-scm.com/docs/git-worktree) is a separate working directory attached to the same Git repository. Each worktree can have its own branch while sharing the repository's Git history. In practical terms, I can work on two branches at once without packing one context away before opening another.

For years, my branch-switching workflow looked like this: stash whatever I was midway through, switch branches, do the thing, switch back, pop the stash, and hope nothing conflicted. It worked, but I was constantly interrupting one task to work on another.

`git worktree` solves this cleanly. I didn't use it sooner for an almost embarrassingly small reason: the command is a bit too much typing for something I want to do without thinking.

```bash theme={null}
git worktree add -b feature-branch-one ../myproject-feature-branch-one
```

The command isn't hard, but it is four things to get right every time: the branch name, the flag, the path, and the naming convention. I wanted the common cases to be a single word, so I built two shell functions: `wtadd` and `wtrm`.

## Starting simple: create and go

The first version of `wtadd` handled the simplest case: take a branch name, build a sibling directory path, create the worktree, and `cd` into it.

```bash theme={null}
wtadd feature-branch-one
```

Two decisions shaped the implementation:

* Sibling directories, not nested ones: I place worktrees alongside the repo (`../myproject-feature-x`) instead of inside it. Nested worktrees can get swept up by linters, file watchers, and IDE indexing.

* A consistent naming pattern: `<repo>-<branch-with-dashes>`. So `feature/auth-refresh` in `myproject` becomes `../myproject-feature-auth-refresh`. The function replaces slashes with dashes.

I initially wrote `wtadd` as a shell script, but ran into a small gotcha: a script run as `./wtadd.sh` changes directory only inside its own subprocess. The new worktree was created successfully, but my terminal stayed where it was. A shell function's `cd` changes the current terminal directory, so I moved `wtadd` into my `.zshrc` to land in the new worktree automatically.

## Handling existing branches

That was enough for new branches, but not for branches that already existed. `wtadd` now checks three things, in order:

1. Does the branch already exist locally? Check it out as-is.
2. Does it exist on `origin` but not locally? Create a local tracking branch.
3. Does it exist nowhere? Create it from `main` by default (or another base branch).

```bash theme={null}
wtadd feature-branch-one              # new, from main
wtadd hotfix-login release-2.3        # new, from release-2.3
wtadd colleagues-existing-branch      # picks it up as-is
```

The detection uses `git show-ref --verify --quiet`. One caveat: case 2 only works if `origin/<branch>` is visible locally, so run `git fetch origin` after someone pushes a new branch.

## Cleanup: `wtrm`

When creating worktrees is easy, removing them should be easy too. That's what the second function handles:

```bash theme={null}
wtrm feature-branch-one
wtrm feature-branch-one --force   # if it has changes or untracked files
```

`wtrm` uses the same sibling-directory convention as `wtadd`. If I'm at the root of the worktree I'm removing, it moves me to the main worktree first because `git worktree remove` can't remove the current directory.

<Note>
  `wtrm` does not delete the branch.
</Note>

## Where it landed

Together, the two functions cover this workflow: start something new or pick up something that exists, and clean up when it's done. They leave Git's worktree behavior unchanged while automating the branch detection, path naming, and command syntax I had to repeat every time.

## Why now?

The reason I finally started using them was seeing [OpenAI's Symphony](https://openai.com/index/open-source-codex-orchestration-symphony/) turn a project-management board into a control plane for coding agents:

<iframe className="w-full h-[900px] rounded-xl" src="https://platform.twitter.com/embed/Tweet.html?id=2048825010371039648" title="OpenAI Developers' Symphony post" frameBorder="0" scrolling="auto" />

Seeing that made the problem concrete: multiple tasks need isolated places to run. Separate clones provide that isolation, but duplicate the repository. Worktrees provide the same separation while sharing the repository's Git history.

This pattern is also showing up in agent tooling. [Cursor](https://cursor.com/docs/configuration/worktrees) and [Conductor](https://www.conductor.build/docs/concepts/git-worktrees) use separate worktrees to isolate tasks and let agents work in parallel without editing the same files. [Cursor CLI](https://cursor.com/docs/configuration/worktrees) and [Claude Code](https://code.claude.com/docs/en/worktrees) offer native worktree commands too, which made the limitations of my single-working-directory workflow more obvious.

I put the complete functions, installation instructions, and examples in [`git-worktree-helpers`](https://github.com/subin23k/git-worktree-helpers). Copy them into your `.zshrc`, and the next time you need to switch branches, create a worktree instead of stashing your work. If you have ideas for improving the helper functions or find an edge case, contributions are welcome.

<Card title="git-worktree-helpers" icon="github" href="https://github.com/subin23k/git-worktree-helpers" cta="View on GitHub" arrow="true">
  Shell helpers for creating and removing Git worktrees.
</Card>
