Summary
Claude Code's EnterWorktree tool creates a git worktree at .claude/worktrees/<name>, inside the repository working tree. jj resolves its workspace by walking up from the current directory, so from inside that worktree it finds the main repo's .jj/ and operates on the default workspace. An agent that believes it is isolated is in fact driving the operator's working copy.
AGENTS.md lines 18-20 tell agents to prefer jj in a co-located repo. That rule is correct, and it is exactly what makes this collision likely: the agent follows the jj instruction inside a directory where jj does not mean what it appears to mean.
Root cause
Two tools disagree about what directory they are in, and only one of them is right.
EnterWorktree's own documentation states the location: "creates a new git worktree inside .claude/worktrees/". Git treats that directory as a separate checkout with its own HEAD. jj has no knowledge of git worktrees, finds .jj/ by upward search, and answers every question about the parent repo.
Evidence
Reproduced in a disposable repo, not inferred from the code.
git init -q -b main . && echo hello > a.md && git add a.md && git commit -qm init
jj git init --colocate
git worktree add .claude/worktrees/test -b testbranch
From inside .claude/worktrees/test, the two tools report different repositories:
$ git rev-parse --show-toplevel
.../wt-repro/.claude/worktrees/test
$ git branch --show-current
testbranch
$ jj workspace root
.../wt-repro # the MAIN repo, not the worktree
Running a single jj command from inside the worktree moves the parent's working copy:
main workspace @ BEFORE: lkslwqpw
$ cd .claude/worktrees/test && jj new -m "isolated-work"
Working copy (@) now at: utsrsqvp 3c575574 (empty) isolated-work
main workspace @ AFTER: utsrsqvp isolated-work
$ jj workspace list
default: utsrsqvp 3c575574 (empty) isolated-work
One workspace exists. The "isolated" commit landed on it. Any of jj new, jj describe, jj commit, jj bookmark set, jj abandon has the same reach.
Secondary effect: git worktree add -b testbranch creates a git branch that jj imports on its next command, so testbranch appears as a bookmark on the main repo's commit and pollutes the bookmark namespace.
What is not a problem
Recording this so nobody re-investigates it. jj does not snapshot the nested worktree's files into the main working copy. jj st stays clean both after git worktree add and after creating a new file inside the worktree. The hazard is command routing, not tree duplication. I assumed the opposite before testing and was wrong.
How it surfaced
A background agent working a merge hand-off on aguil/notes PR #2. Its fork reminder instructed it to isolate with EnterWorktree. It recognised the co-location hazard, used jj workspace add in a sibling directory instead, and the parent session's @ survived intact. It got that right by reasoning, not because anything told it to. The next agent may not.
Proposed fix
1. Name the tool in AGENTS.md. The jj co-location paragraph currently says to prefer jj and avoid raw git that rewrites state. Extend it:
Do not use EnterWorktree in a co-located repo. It creates a git worktree inside .claude/worktrees/, where jj still resolves to the main workspace, so "isolated" jj commands move the operator's working copy. Isolate with jj workspace add ../<name> in a sibling directory, and jj workspace forget <name> plus removing the directory when finished.
2. Consider a rules module. dot_agents/rules/worktree-isolation.md, following the live-interaction-safety.md pattern already referenced from AGENTS.md, if the guidance grows past a paragraph.
3. Investigate a hook, which would be the stronger fix. Instruction-level guidance depends on the agent reading and obeying it. EnterWorktree's documented requirement is "Must be in a git repository, OR have WorktreeCreate/WorktreeRemove hooks configured in settings.json", and it says it delegates to those hooks "outside a git repository". Whether a configured hook takes precedence inside a git repo is not documented either way, and I have not tested it. If it does, a WorktreeCreate hook that shells out to jj workspace add when .jj/ is present fixes this mechanically rather than by asking nicely. Worth one experiment before writing prose. No worktree hooks are configured today; settings.json carries only a Stop hook.
Acceptance criteria
Summary
Claude Code's
EnterWorktreetool creates a git worktree at.claude/worktrees/<name>, inside the repository working tree. jj resolves its workspace by walking up from the current directory, so from inside that worktree it finds the main repo's.jj/and operates on the default workspace. An agent that believes it is isolated is in fact driving the operator's working copy.AGENTS.mdlines 18-20 tell agents to preferjjin a co-located repo. That rule is correct, and it is exactly what makes this collision likely: the agent follows the jj instruction inside a directory where jj does not mean what it appears to mean.Root cause
Two tools disagree about what directory they are in, and only one of them is right.
EnterWorktree's own documentation states the location: "creates a new git worktree inside.claude/worktrees/". Git treats that directory as a separate checkout with its ownHEAD. jj has no knowledge of git worktrees, finds.jj/by upward search, and answers every question about the parent repo.Evidence
Reproduced in a disposable repo, not inferred from the code.
From inside
.claude/worktrees/test, the two tools report different repositories:Running a single jj command from inside the worktree moves the parent's working copy:
One workspace exists. The "isolated" commit landed on it. Any of
jj new,jj describe,jj commit,jj bookmark set,jj abandonhas the same reach.Secondary effect:
git worktree add -b testbranchcreates a git branch that jj imports on its next command, sotestbranchappears as a bookmark on the main repo's commit and pollutes the bookmark namespace.What is not a problem
Recording this so nobody re-investigates it. jj does not snapshot the nested worktree's files into the main working copy.
jj ststays clean both aftergit worktree addand after creating a new file inside the worktree. The hazard is command routing, not tree duplication. I assumed the opposite before testing and was wrong.How it surfaced
A background agent working a merge hand-off on
aguil/notesPR #2. Its fork reminder instructed it to isolate withEnterWorktree. It recognised the co-location hazard, usedjj workspace addin a sibling directory instead, and the parent session's@survived intact. It got that right by reasoning, not because anything told it to. The next agent may not.Proposed fix
1. Name the tool in
AGENTS.md. The jj co-location paragraph currently says to preferjjand avoid rawgitthat rewrites state. Extend it:2. Consider a rules module.
dot_agents/rules/worktree-isolation.md, following thelive-interaction-safety.mdpattern already referenced fromAGENTS.md, if the guidance grows past a paragraph.3. Investigate a hook, which would be the stronger fix. Instruction-level guidance depends on the agent reading and obeying it.
EnterWorktree's documented requirement is "Must be in a git repository, OR have WorktreeCreate/WorktreeRemove hooks configured insettings.json", and it says it delegates to those hooks "outside a git repository". Whether a configured hook takes precedence inside a git repo is not documented either way, and I have not tested it. If it does, aWorktreeCreatehook that shells out tojj workspace addwhen.jj/is present fixes this mechanically rather than by asking nicely. Worth one experiment before writing prose. No worktree hooks are configured today;settings.jsoncarries only aStophook.Acceptance criteria
AGENTS.mdnamesEnterWorktreeas unsafe in a co-located repo and givesjj workspace addas the replacement, including the cleanup step.