Git worktrees share the object database with the main repo, but they get their own working directory. That means untracked files — anything not committed — don’t exist in the worktree. The proj/ and wksp/ symlinks that ctx.py uses to find the companion workspace are exactly this: untracked, excluded via .git/info/exclude, invisible to every worktree.

The symptom was that every session in a worktree triggered the “No workspace configured” prompt, even though the main repo had a perfectly good workspace set up. ctx.py was looking for symlinks at git rev-parse --show-toplevel — which returns the worktree root, not the main working tree.

The fix reads symlinks from the main working tree instead. The design question was how to find that root. Two options: git rev-parse --git-common-dir (returns the shared .git path — take its parent) or git worktree list --porcelain (first entry is always the main working tree). I went with worktree list because --git-common-dir assumes .git’s parent is the working tree root, which breaks with --separate-git-dir. We don’t use that, but the assumption felt unnecessary when a direct answer was available.

The subtler decision was what PROJECT and WORKSPACE should mean in a worktree. The worktree is where files live, where git operations target the branch, where .meta and JOURNAL.md are checked out. It’s the working repo. So PROJECT stays as cwd_root (the worktree) — only the symlink lookup location changes to the main root. The resolved companion repo is the same either way.

Two new output fields — IN_WORKTREE and MAIN_WORKTREE_ROOT — give downstream skills the context they need. The IntelliJ MCP entries in the garden document a whole class of “edits landed in the wrong project” bugs when worktrees are involved; having the main root available should help skills pass the right project_path.

The open question is whether this pattern needs to extend further. ctx.py is the path resolver, but WORKSPACE_OK (the fast-path check that decides whether to offer workspace setup) had the same blind spot — it was checking for symlinks at CWD, not the main root. Fixed in the same commit, but it’s worth asking: how many other CWD-relative checks across the skill ecosystem have the same assumption?


<
Previous Post
Why Your Agent Forgets What It Just Did
>
Next Post
When Your Advisor Learns to Act