Going Parallel: Git Worktrees, or How to Be Several Developers at Once

Part 6 of The Thingino Developer’s Journey / <- Part 5

By now you have a problem, and it’s a good one: too many irons in the fire. A camera port waiting on review feedback. A streamer feature mid-refactor. A user in Discord with a bug that needs a quick fix now. And if you’ve started using AI coding agents, you may have several of them wanting to work at the same time, in the same tree, on the same files. That way lies madness.

The traditional answer — git stash, switch branch, rebuild everything, fix, switch back, stash pop, rebuild everything again — is miserable in any project. In a firmware project where a context switch can cost a 30-minute rebuild, it’s disqualifying. And if an AI agent had twenty minutes of context built up in that tree, the branch switch just lobotomized it.

Worktrees in one paragraph

A git worktree is an additional working directory attached to the same repository. One .git object store, one shared history — but each worktree has its own checked-out files, its own branch, its own build state. Work in one is invisible to the others until it’s committed. It’s the isolation of multiple clones without the duplication, and it’s been hiding in git since 2015:

thingino-firmware/                    <- main checkout (master, make update lives here)
thingino-firmware-feat-motion/        <- worktree: feature work
thingino-firmware-fix-rtsp-crash/     <- worktree: that Discord bug
thingino-firmware-port-newcam/        <- worktree: camera port

The working model that makes this scale — for humans and agents alike:

One task, one branch, one worktree, one agent.

Each task gets a directory. Each directory gets one occupant. Nobody stashes, nobody switches, nobody overwrites anyone’s work. Abandoning an experiment is rm-grade cheap; the branch and its commits survive in the shared history.

Why this is especially good in Thingino

Here’s the part that surprises people: Thingino was accidentally worktree-ready all along.

  • Builds can’t collide. Output paths include the branch name: output/<branch>/<camera>-.../. Two worktrees building the same camera on different branches write to different directories by construction.
  • The expensive stuff is shared. ccache lives in ~/.buildroot-ccache, so a “clean” build in a fresh worktree is mostly cache hits. The download cache and prebuilt toolchains can be shared too. A new worktree costs about a checkout, not a first-build.
  • Long builds stop being hostages. Kick off a full build in one worktree, keep editing in another. Your hotfix no longer waits for your feature’s linker.

But a bare git worktree add is not enough here, and this is the one place where Thingino needs you to know three things: the buildroot/ submodule must be initialized per-worktree, Thingino’s Buildroot patches must be applied to it, and the dl/ cache should be shared rather than re-downloaded. That’s exactly why the tree ships a helper.

The helper: scripts/worktree.sh

# From the main checkout: create a task worktree
scripts/worktree.sh create feat/motion-tuning

# It: creates ../thingino-firmware-feat-motion-tuning on a new branch,
# initializes buildroot (sharing objects with your main checkout - no
# re-clone), applies the Buildroot override patches, and symlinks dl/.

cd ../thingino-firmware-feat-motion-tuning
CAMERA=atom_cam2_t31x_gc2053_atbm6031 make fast     # business as usual

At the end of a session, keep the branch from drifting away from master:

scripts/worktree.sh sync      # rebase onto origin/master, re-sync buildroot
git push --force-with-lease   # after a rebase, always with-lease

And when the PR merges:

cd ../thingino-firmware                      # back to the main checkout
scripts/worktree.sh remove feat/motion-tuning    # directory gone, branch preserved

scripts/worktree.sh list shows the whole fleet.

The house rules

Learn these once and worktrees will never bite you:

  1. Never run make update in a feature worktree. It git pull --rebasees whatever branch you’re on. Update the main checkout with make update; sync feature worktrees with worktree.sh sync.
  2. A branch lives in one worktree at a time. fatal: already checked out isn’t an error, it’s a bodyguard — it makes the two-agents-one-branch disaster structurally impossible.
  3. Gitignored files don’t travel. Your local.mk, user/ layers, and overrides/ don’t appear in new worktrees. Usually that’s what you want — a task worktree should be close to pristine. Set up per-worktree copies only when the task needs them, and never point two worktrees at the same overrides/<pkg>/ checkout (that’s shared mutable state, the exact thing we came here to eliminate).
  4. Container builds want the main checkout. A linked worktree’s .git is a pointer file to the main repo — which isn’t mounted inside the build container. Build on the host in worktrees; use build-container.sh from the main checkout.

And now, the agents

This workflow is how you run multiple AI coding agents on Thingino without chaos. Give each agent its own worktree, its own task, and explicit scope (“you own ../thingino-firmware-fix-rtsp-crash, this is the acceptance criterion, every other directory is read-only”). Agents keep their context because nobody switches branches under them; agents can’t corrupt each other because they’re in different directories; reviewing an agent’s work is reviewing a branch — the same PR flow you’d use for a human.

While a long build runs, lock the worktree so nothing prunes it out from under the compiler:

git worktree lock ../thingino-firmware-feat-motion-tuning --reason "build running"

Your role shifts from typist to tech lead of a very fast, very literal team: scope tasks, review diffs, merge, delete the worktree, repeat.

The full arc

Look at where you started: cloning a repo and waiting an hour for a build. Now you’re running a main checkout that tracks master, three task worktrees building three cameras in parallel, a package override with real git history, and an agent grinding through a refactor in a directory it can’t escape — while you review the camera port that just got its second thumbs-up.

That’s the whole journey. The deep reference lives in docs/worktrees.md; the rest lives in docs/, in the Discord, and in the boot logs of cameras that don’t phone home anymore — partly because of you.

What you learned

  • Worktrees: one repo, many working directories, zero stashing.
  • Thingino is naturally worktree-friendly (branch-scoped output, shared caches) — and scripts/worktree.sh handles the parts that aren’t (submodule, patches, dl).
  • House rules: no make update in worktrees, one branch per worktree, gitignored files don’t travel, containers stay in the main checkout.
  • One task, one branch, one worktree, one agent.

— The End. Now go port something.