Development worktrees¶
Implementation work happens in a git worktree, not in the primary checkout.
The repository enforces this: a reference-transaction hook blocks direct
feature-branch creation. The workflow exists so several agents (and several
humans) can edit the same repository concurrently without stepping on each
other's files or branches.
The driver is scripts/mini_ork_worktree.py; the make targets wrap it.
The loop¶
flowchart LR
C[make worktree SLUG=x] --> W[edit + commit in the worktree]
W --> M[make worktree-merge SLUG=x]
M --> R[rebase origin/main]
R --> G[green gate]
G --> P[push HEAD:main]
P --> F[fetch + ff-only local main]
F --> D[make worktree-clean SLUG=x]
make install-hooks # once per clone; activates .githooks/
make worktree SLUG=my-change # create worktree + branch
# ... edit and commit inside the worktree ...
make worktree-merge SLUG=my-change # rebase, gate, push to main
make worktree-clean SLUG=my-change # remove worktree, delete branch, release claims
Worktrees live under /Volumes/docker-ssd/ps/mini-ork-worktrees/<slug> by
default. Override the parent directory with MINI_ORK_WORKTREES_DIR. Branches
are named wt/<slug>.
What create does¶
- Takes a lock around the claim check and registration.
- Refuses if the slug already has a live worktree or if a requested ownership claim overlaps one already held.
- Creates the worktree and branch from the current
origin/main. - Records ownership claims in the worktree's
.ownershipTSV.
--owns (repeatable) claims a file surface. Claims are path-prefix aware: a
claim on mini_ork/dispatch overlaps a claim on
mini_ork/dispatch/retry.py. A second worktree whose claim overlaps a live one
is refused, so two agents cannot race the same file. Claims are released by
clean, by release, or automatically when the worktree directory disappears.
Inspecting claims¶
python3 scripts/mini_ork_worktree.py owners
python3 scripts/mini_ork_worktree.py owners --json
python3 scripts/mini_ork_worktree.py list
python3 scripts/mini_ork_worktree.py release <slug>
make wraps only create, merge and clean; owners, release and list
are invoked through the driver directly.
owners shows which slug holds which path. When a worktree is abandoned, its
claims block new work until released; check owners before assuming a path is
free.
What merge does¶
- Takes the same lock.
- Fetches
origin/main— the fetch happens inside the critical section, so a rebase never targets a ref that went stale during a long gate. - Rebases the branch onto
origin/main. - Runs the green gate outside the lock:
python3 -m pytest -qby default. - Pushes
HEAD:mainwith a bounded retry (MO_MERGE_PUSH_ATTEMPTS, default 5).
The gate runs outside the lock so a long suite does not block another worktree
command. The push retry handles a concurrent push: after a rejected push the
driver re-fetches and rebases onto the moved main inside the lock, then pushes
again. The gate is deliberately not re-run after that rebase — re-gating
reopens the very window it was meant to close. The accepted trade-off is that a
late rebase can combine your branch with upstream commits that landed during
the gate; those upstream commits were gated when they landed.
Scoping the gate¶
Scope the gate per task with MINI_ORK_TEST_CMD:
MINI_ORK_TEST_CMD="python3 -m pytest tests/unit/test_retry.py -q" \
make worktree-merge SLUG=retry-fix
A scoped gate is evidence for the files it ran, not for the repository. Use it when the change is genuinely local; run the broader unit layer for changes to shared modules. See Testing.
Merge discipline¶
The mechanical part is automated; these rules are the part that still goes wrong.
Stage explicit paths¶
Never git add -A or git add .. Either sweeps other sessions' work into
your commit. Stage the files you changed:
git add mini_ork/dispatch/retry.py tests/unit/test_retry.py
git commit -m "retry: bound the backoff"
git add <dir> is acceptable only for a directory you own end to end.
Check the diff before committing¶
If the diff contains files you never touched — especially deletions of files you did not author — your base is stale. Rebase; do not merge your way past it.
Refresh local main after a merge¶
push HEAD:main updates the remote; your local main stays where it was. Run:
A worktree created from a stale local main starts behind and reintroduces the
divergence.
If --ff-only refuses, stop¶
That refusal means local main and origin/main diverged — someone pushed
while you worked. Do not run a plain git merge origin/main, do not
git reset --hard, do not bypass hooks. Reconcile so every commit on both
sides survives. A merge commit keeps all of them; a rebase rewrites them, and
you must never rewrite a commit that is not yours.
Fix forward, never revert main¶
If a post-merge smoke check fails, fix it from a new worktree off the latest
main. Do not revert main and do not reset it.
Cleaning up¶
This removes the worktree directory, deletes the local branch, and releases the slug's claims. It does not push anything; merge first.
Clean removes the directory
clean deletes the worktree. Commit and merge first, or copy out anything
you need. Do not clean a worktree while a run is using it — the run's
engine pointer and artifacts live under that path.
Concurrency and locking¶
The driver takes an mkdir lock — $MINI_ORK_WORKTREE_LOCK, default
$TMPDIR/mini-ork-worktree.lockdir — around two check-then-act pairs:
- claim check plus registration on
create; - fetch, rebase and push on
merge.
It waits up to 30 seconds for the lock. Two concurrent agents therefore cannot both pass an ownership check or interleave a stale push. If the lock times out, another worktree command is running; wait and retry rather than deleting the lock directory.
The lock does not cover your edit-commit loop. It covers the operations that read and write shared refs.
Working inside the worktree¶
A worktree is a full checkout with its own working tree but a shared git object store. Two consequences:
- Do not run
git stashinside a worktree. Stashes are shared across worktrees, and a stash from one branch can unexpectedly apply in another. Commit or discard; do not stash. - The branch is shared, the files are not. Editing files in one worktree
does not change another's checkout, but committing on
wt/<slug>is visible to anyone who checks out that branch.
Each worktree may also have its own .mini-ork/ home. That is the point: two
runs and two gates do not share a state database or run directory.
When create is refused¶
| Message | Cause | Fix |
|---|---|---|
| claim overlaps | Another live worktree owns that path | Finish or release it, or narrow your claim |
| worktree exists | The slug is already live | Use the existing one or pick a new slug |
| reference-transaction blocked | Hooks are not installed or a direct branch was attempted | make install-hooks; create through make worktree |
| lock timeout | Another worktree command is running | Wait and retry |
Shipping¶
Per the repository's operating rules: a finished, verified change is committed,
merged and pushed — not left parked in a worktree. Commit and merge without
being asked, and keep main clean.
Next¶
- Testing — the gate and its traps.
- Extending mini-ork — what to change.
- Recovery — when a run, not a branch, is stuck.