Isolated Development with Flox
Contents
This guide explains how to create isolated PostHog development environments using Flox and Git worktrees for seamless branch switching.
This guide builds on the developing locally setup. You should have Flox installed and be able to run
hogli start(PostHog's developer CLI, available inside the Flox environment) before proceeding.
Key Benefits:
- Work on multiple branches simultaneously with isolated environments
- Each worktree has its own Flox environment and Python dependencies
- Quick switching between features, bug fixes, and PR reviews
- Standard
hogli startcommand works in each worktree
Important: Only one PostHog instance (
hogli start) can run at a time since they all use the same ports. The workflow focuses on quickly stopping one instance and starting another.
Light worktrees
You only need the full setup below when you want to run the app in a worktree. For edit and commit workflows, a plain git worktree add <path> <branch> is enough:
- Pre-commit hooks (lint-staged, hogli, ruff, ty) work out of the box: they borrow the main clone's
node_modulesand Python venv automatically, as long as the worktree'spnpm-lock.yamlanduv.lockmatch the main clone's. - The borrow lasts for one hook process. The worktree gets no
node_modulesor venv of its own, so editors, typecheck and tests needflox activatefirst. - Borrowed tooling runs the main clone's copy of first-party code.
hogliand@posthog/*resolve there rather than to your branch, because editable installs and workspace links point at whichever clone ran the install. Runflox activatein the worktree before changinghoglior a workspace package. - If a lockfile differs, the hooks tell you what to install locally instead.
- Use
phw(below) only when you need to run the app in that worktree.
Prerequisites
- Flox installed: https://flox.dev/docs/install-flox/
- Git worktrees support (Git 2.5+)
- GitHub CLI (for PR checkout):
brew install gh - jq (for PR JSON parsing):
brew install jq - direnv (recommended):
brew install direnv
Configuration
Worktree Location
By default, worktrees are created in ~/.worktrees/posthog/. You can customize this location by setting the POSTHOG_WORKTREE_BASE environment variable:
For example:
Quick Start
1. One-Time Setup
In all these examples, replace ~/dev/posthog/posthog with your local path to the PostHog repo.
2. Daily Workflow with phw
After setup, use the phw command for everything:
Create a NEW branch
Work on EXISTING branch
Switch to EXISTING worktree
Review a Pull Request
Real-World Example
Common Workflows
Switching Between Worktrees
Since you can only run one PostHog instance at a time, the workflow focuses on quickly switching between isolated environments:
Managing Your Worktrees
Worktree Location Management
The phw list and phw remove commands work with all your PostHog worktrees, regardless of where they were created. This is helpful if you:
- Changed your
POSTHOG_WORKTREE_BASEsetting after creating some worktrees - Have worktrees in multiple locations
- Want to clean up old worktrees from previous setups
Example scenario:
This uses Git's native worktree tracking (git worktree list) rather than trying to guess paths.
Quick Reference
Commands
Workflow Benefits
- Isolated environments: Each worktree has its own Flox environment and Python dependencies
- Quick switching: Move between branches without losing work or rebuilding dependencies
- Clean separation: Different features, bug fixes, and PR reviews stay completely separate
- Standard tools: Uses familiar
hogli startcommand in each worktree
How It Works
- direnv +
.envrc: Automatically activates Flox when you enter any worktree directory - Flox Environment: Each worktree gets its own
.flox/env/manifest.tomland Python venv - UV Caching: Flox's
uv syncuses its own caching, so dependencies are efficiently shared - Git Worktrees: Each branch lives in its own directory with isolated Git state
phwfunction: Provides auto-cd functionality and smart tab completion- Git-native Management:
phw listandphw removeuse Git's authoritative worktree tracking, working with worktrees from any location
The Magic Flow
Interactive Environment Switching
When you switch between worktrees while already in a Flox environment, you'll see an interactive prompt to prevent unexpected nested environments:
Your options:
- Press Enter or 'n' (recommended): Skips activation. Run
exitfirst to cleanly switch environments - Type 'y': Proceeds with nested activation (you'll need multiple
exitcommands later) - Ctrl+C: Cancels direnv entirely so you can run
exitand retry
Best practice: When switching between worktrees, exit your current Flox environment first:
Tips and Tricks
Tab Completion
The phw function includes smart tab completion:
phw create <TAB>- Suggests "haacked/" prefixphw checkout <TAB>- Shows all available branchesphw remove <TAB>- Shows removable worktrees
Resource Management
Debugging
Troubleshooting
direnv not activating
Flox activation fails
phw command not found
Make sure the Flox environment is activated:
Dependencies out of sync
Interactive Flox prompt behavior
Problem: You see the interactive prompt every time you switch directories
Solution: This is expected behavior when switching between worktrees. Choose the best approach:
Clean Up
To clear old phw, Claude Code (including agent scratchpads), Codex, and PostHog Code worktrees in bulk, run hogli worktrees:clean --before 7d --mode deps,build --dry-run to preview, then drop --dry-run, or use --mode full to remove whole worktrees.
--mode deps removes installed dependencies such as node_modules, Python venvs, and Cargo target dirs.
--mode build removes build output and tool caches such as Cargo target, dist, .next, and __pycache__, but only where git ignores every file inside.
Neither removes a folder that holds a tracked file.
Every mode skips a worktree where cargo or rustc is running.
--mode deps and --mode build keep a Cargo target that a build used after the cutoff.
Without --source or --repo, they also offer to delete the old shared ~/.cargo/target when CARGO_TARGET_DIR is not set and no build used it after the cutoff.
For a Cargo target that several projects share, run hogli doctor:disk --area cargo-units. It removes only the build units that no build in the last 7 days needed, directly or through its dependencies.
Every mode also removes folders of removed worktrees that hold only flox caches.
--mode deps and --mode build also act on worktrees that git worktree list registers outside these folders, such as ones made by hand. --mode full and the daily background run act on those only with --source other.
Flox activation also runs this once a day in the background: --mode deps,build on worktrees idle for 7 days and --mode full on worktrees idle for 14 days.
It skips worktrees with uncommitted or unpushed work and keeps every branch.
Commits that landed through a squash merge do not count as unpushed: hogli matches the branch's diff against the squash commit, and asks the GitHub CLI for the merged pull request when that fails.
Set POSTHOG_WORKTREES_AUTOCLEAN=0 to turn it off.
The output goes to .flox/cache/worktrees-clean.log in the checkout that ran it.
Quick Setup Script
For a complete one-time setup, run:
After setup, you're ready to use commands like:
phw create haacked/feature(create from master)phw create haacked/feature my-branch(create from my-branch)phw checkout my-branchphw pr 12345





