Fan-out worktrees live in a repo-owned .worktrees.<repo-name> root (0.20.1)#89
Conversation
…ktrees.<repo-name> root (0.20.1) Every worktree a fan-out creates now goes under one repo-owned root beside the repo directory: <parent-of-repo>/.worktrees.<repo-name>/agent-<task-id>, resolved at runtime rather than hardcoded. Two paths that were previously in use are now explicitly forbidden: - A shared ../worktrees/ (the SKILL.md convention). In a multi-repo parent it belongs to nobody, so it reads as deletable scratch space to any other agent or human cleaning up; and because task ids restart at t1 in every repo and every plan, two concurrent fan-outs resolve to the identical directory. - Any in-repo path such as .claude/worktrees/ (the worked-example convention), where `git add -A` sweeps N agent checkouts into the PR and `git clean -fdx` destroys live agent work. Cleanup removes only the worktrees a run created — never the root itself and never another repo's root, since a concurrent fan-out may be live inside it. The rule is taught in every place an operator meets it: the assign-to-workforce SKILL.md (fan-out, removal, hard rules, worked example), the split-plan fan-out instructions, CLAUDE.md, docs/skills.md, and `devague learn` (new worktree_location guidance + the learn skills authoring recipe), pinned by a test that asserts both the path and its rationale. docs/assign-to-workforce-worked-example.md records a real past run, so its historical commands are kept intact and annotated with a note pointing at the current convention rather than rewritten. Not addressed here: branch names still collide across plans within a repo (agent/t1 restarts per plan) — a separate follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QTCPH6y6vzAAiKDvhfzub
|
/agentic_review |
PR Summary by QodoNamespace fan-out worktrees under repo-owned .worktrees. root
AI Description
Diagram
High-Level Assessment
Files changed (10)
|
Code Review by Qodo
1.
|
`devague learn` printed a wt_root example referencing $repo_root without defining it. Copied verbatim the snippet does not fail loudly: `dirname ""` is `.` and `basename ""` is empty, so wt_root silently becomes "./.worktrees." — a relative, in-repo path with no repo name, i.e. precisely the layout this guidance exists to forbid. Teach both steps (repo_root=$(git rev-parse --show-toplevel), then wt_root), matching the working examples already in CLAUDE.md and the assign-to-workforce skill, and name the silent-failure mode so the two-step form doesn't read as ceremony. Pinned by test_learn_worktree_snippet_is_self_contained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QTCPH6y6vzAAiKDvhfzub
|
FIX — valid, and the failure mode is worse than "will typically error." It fails silently. With A relative, in-repo path with no repo name — precisely the layout this whole change exists to forbid, since Fixed in 2208e16: repo_root=$(git rev-parse --show-toplevel)
wt_root="$(dirname "$repo_root")/.worktrees.$(basename "$repo_root")"I also named the silent-failure mode inline in the guidance text, so the two-step form doesn't read as ceremony a future editor might compress back down, and pinned it with
|
|



What
Pins every worktree an
assign-to-workforcefan-out creates to one repo-ownedroot beside the repo directory —
<parent-of-repo>/.worktrees.<repo-name>/agent-<task-id>— resolved at runtime, never hardcoded:
Why
The repo carried two conflicting conventions, and both were hazardous:
assign-to-workforce/SKILL.md../worktrees/agent-<id>t1in every repo and every plan, two concurrent fan-outs resolve to the identical directory.docs/assign-to-workforce-worked-example.md.claude/worktrees/agent-<id>git add -Asweeps them into the PR,git clean -fdxdestroys live agent work.A repo-named root fixes all three: ownership is visible, collisions are
impossible, and nothing inside the repo working tree can reach it.
Cleanup is now scoped too — remove only the worktrees a run created, never
rm -rfthe root itself and never another repo's root, since a concurrentfan-out may be live inside it.
Where it's taught
The rule lands in every place an operator meets it, so no surface can drift:
.claude/skills/assign-to-workforce/SKILL.md— fan-out step 1, the removalstep, a new hard rule, and the worked example.
scripts/assign-to-workforce.sh— thesplit-planfan-out instructions thehuman reads at gate 2.
CLAUDE.md— new "Where worktrees live" section with both forbidden pathsand why each fails.
devague/cli/_commands/learn.py— newworktree_locationguidance, plus thelearn skillsauthoring recipe.docs/skills.md.Pinned by
test_learn_names_repo_owned_worktree_root, which asserts both thepath and its rationale survive — the reasoning is what stops a future edit
from quietly reverting to a shared directory.
Judgment calls worth reviewing
docs/assign-to-workforce-worked-example.mdkeeps its historical commands.It opens with "This document records a real end-to-end run" — rewriting the
commands would falsify the record. It gains a note pointing at the current
convention instead. Push back if you'd rather it be updated in place.
agex pr lintviolation is left alone: auser-dotfile-referenceonCHANGELOG.md(the~/.eidetic/memoryline,line 286 on
main). It's a released changelog entry describing a real path;editing it would falsify shipped history. Not introduced by this branch.
directories no longer collide, but branches (
agent/t1,agent/t2, …)still do across plans within one repo, since ids restart per plan — a second
fan-out hits
fatal: a branch named 'agent/t1' already exists. Worth afollow-up (
agent/<plan-slug>/<task-id>).Alignment
This changes an origin skill (
assign-to-workforce) that guildmasterre-broadcasts to the mesh, plus
CLAUDE.md— so consumers need the updatedskill.
agex pr deltacouldn't run here (no.claude/skills.local.yamlonthis machine), so the sibling sweep is unverified and should be driven from
guildmaster.
Verification
uv run pytest -n auto— 690 passed.flake8/black/isortclean;markdownlint-cli20 errors.agex pr lint— clean except the pre-existing CHANGELOG line above.Version bumped 0.20.0 → 0.20.1 with a CHANGELOG entry.