Coding agents are making `git worktree` popular because worktrees let
multiple agents work on the same repository at the same time without interfering with
each other. Each branch is checked out in its own directory: if your
*main* branch lives in `main/`, a `feature-1` branch can
sit right next to it in `feature-1/`.
However, directory isolation doesn't isolate network ports. When a server in one
worktree runs on port `8080`, starting the server in another worktree fails
because the address is already in use.
At first, I passed ports manually per worktree (like `PORT=8001 make run`),
which got tedious. Next, I decided to calculate a deterministic port number for each
worktree, using its absolute directory path. Because each worktree lives in a different
directory, every environment gets its own port automatically.
Hashing the directory path in your Makefile #
To turn a directory path into a port number, take the SHA-256 hash of the current
working directory, convert the hex digest to an integer, and map it into 400 buckets of
5 ports across the `8000` to `9999` range:
full_path = os.getcwd().encode("utf-8")
path_hash = int(hashlib.sha256(full_path).hexdigest(), 16)
port = 8000 + ((path_hash % 400) * 5)
Spacing base ports 5 numbers apart (`8000`, `8005`,
`8010`, and so on) gives you 400 buckets, making collisions across a handful
of local worktrees unlikely while reserving four adjacent ports (`PORT + 1`
through `PORT + 4`) for secondary services.
To avoid changing your application code, you can compute `PORT` directly in
your `Makefile` and export it as an environment variable:
ifeq ($(PORT),)
export PORT := $(shell python3 -c "import hashlib, os; h = hashlib.sha256(os.getcwd().encode('utf-8')).hexdigest(); print(8000 + ((int(h, 16) % 400) * 5))")
else
export PORT
endif
.PHONY: run
run:
@echo "Starting server on http://localhost:$(PORT)"
@uvicorn main:app --host 0.0.0.0 --port $(PORT)
If `PORT` isn't already set, Make runs the Python command once
(`:=`) to calculate the port and exports it to child processes. If you pass a
port manually (like `PORT=9000 make run`), the `else` branch keeps
your value and exports it instead. Using `ifeq` with `:=` rather
than `PORT ?=` avoids re-running the Python subshell every time
`$(PORT)` is referenced.
Offsetting ports for secondary services #
Because each worktree gets a 5-port bucket, you can assign up to four secondary services
to `PORT + 1` through `PORT + 4` without colliding with another
worktree's base port. Here's an example with the Firestore emulator:
FIRESTORE_PORT ?= $(shell expr $(PORT) + 1)
export FIRESTORE_EMULATOR_HOST ?= 127.0.0.1:$(FIRESTORE_PORT)
.PHONY: emulator
emulator:
@gcloud emulators firestore start --host-port=$(FIRESTORE_EMULATOR_HOST)
expr $(PORT) + 1 adds 1 to the base port, and exporting
FIRESTORE_EMULATOR_HOST tells the Google Cloud client libraries in your app
and test runner which emulator port to connect to automatically. Passing
--host-port=$(FIRESTORE_EMULATOR_HOST) binds the Firestore emulator to that
same address on startup.