This year has changed the way I do software development completely. Last year I was using Claude Code but I still used an IDE and did at about 20-30% of the work myself either because I was fixing up Claude’s work or it was small enough that it didn’t seem worth it to spin up an agent.
This year however, I have barely written a single line of code. I don’t even do git commits myself anymore. I’ve completely dropped my IDE and I just work in the terminal with Claude, neo-vim and lazygit.
Another thing that’s changed is that I’m routinely working on multiple things at the same time. I was doing this last year too but the IDE was a bottleneck since I didn’t want to have to start a new IDE window for each parallel project. Now that I don’t use an IDE it doesn’t matter.
Enter worktrees
I’ve found that Git worktrees are essential to doing parallel developement. I almost never do work in the main repo root anymore because it makes creating new worktrees harder. Managing all these worktrees was becoming a headache, especially setting up the environment each time. I started using worktree-cli along with a worktrees.json file to handle the project setup:
{
"setup-worktree": [
"cp $ROOT_WORKTREE_PATH/.env .env 2>/dev/null || echo 'No .env found in main worktree; skipping'",
"cp $ROOT_WORKTREE_PATH/.envrc .envrc 2>/dev/null || true",
"cp $ROOT_WORKTREE_PATH/.python-version .python-version 2>/dev/null || true",
"command -v direnv &> /dev/null && direnv allow",
"bash scripts/bootstrap.sh"
]
}
The bootstrap.sh script does some checks and runs things like uv sync and npm ci.
This works really well and my new worktree command is now just wt setup <branch name> --trust.
Enter Claude
Last week Claude Code released full support for managing worktrees within Claude by passing the --worktree CLI flag. This works well but there is no setup hook (yet).
The solution I came up with was to use the SessionStart hook with a script that will check if it is running in a worktree. Here’s the pattern:
#!/usr/bin/env bash
# Run on SessionStart to set up the development environment.
# In a worktree: copies env files from the main worktree first.
# Always: installs dependencies via bootstrap.sh.
set -euo pipefail
ROOT_WORKTREE_PATH=$(git worktree list --porcelain | awk '/^worktree/{print $2; exit}')
CURRENT_PATH=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
if [ "$CURRENT_PATH" != "$ROOT_WORKTREE_PATH" ]; then
echo "Setting up worktree at $CURRENT_PATH"
export ROOT_WORKTREE_PATH
[ -f "$ROOT_WORKTREE_PATH/.env" ] && [ ! -f ".env" ] && cp "$ROOT_WORKTREE_PATH/.env" .env
[ -f "$ROOT_WORKTREE_PATH/.envrc" ] && [ ! -f ".envrc" ] && cp "$ROOT_WORKTREE_PATH/.envrc" .envrc
command -v direnv &>/dev/null && direnv allow
fi
"$ROOT_WORKTREE_PATH/scripts/bootstrap.sh"
echo "Setup complete."
Now update the Claude settings to call that hook:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/setup-env.sh"
}
]
}
]
}
}
And we can use the same script in the worktrees.json file to avoid duplication:
{
"setup-worktree": [
"bash .claude/hooks/setup-env.sh"
]
}
Now both worktree-cli and claude -w get properly configured environments automatically.