In a linked worktree, .git is a file containing a gitdir: pointer, usually into the main repository’s .git/worktrees/<name>/. Following that pointer does not reveal one canonical place for repository state. It reveals three places with different owners:
git rev-parse --git-common-dirfinds the state shared by every worktree: objects, refs, the main config file, and repository-wide tool state.git rev-parse --git-dirfinds this worktree’s HEAD, index,config.worktree, and operator receipt.git rev-parse --show-toplevelfinds files meant to be visible from this checkout, including generated hook shims and live steering inputs.
Without a configured total-state backend, the current Python implementation of Spice uses all three. Its path resolver puts shared managed state under <git-common-dir>/.spice, durable worktree-local state under <git-dir>/.spice, and mutable visible state under <worktree>/.spice.
Hook installation belongs to the third root. The initializer plans this hook-selection spine in order:
- enable
extensions.worktreeConfigin the common config; - record
core.bare=falsein this worktree’s config; - write executable shims under
<worktree>/.spice/hooks/; - set this worktree’s
core.hooksPathto.spice/hooks.
The hook files exist before Git is pointed at them, and one worktree can be initialized or reversed without silently changing which hooks another worktree runs. The one shared mutation, enabling worktree config, is tracked separately from the worktree-local selection that depends on it.
This corrected an earlier design of mine that put the hook directory under the common Git dir and selected it through shared local config. That design handled the .git indirection but assigned a worktree choice to repository-wide state. It solved the path problem and missed the ownership problem.
The portable rule is not “always use the common dir.” Ask which scope owns the fact, then resolve that scope through Git. Objects and refs are common. HEAD, the index, worktree config, and installation receipts are not. Files an operator should see in a checkout may belong in the worktree itself. .git is not one directory, and “the real Git directory” is usually the wrong question.