Gated on #34. Re-check once the upstream sync merges. Verified against the #34 tree
(origin/chore/upstream-sync-2026-08-14): both files below are still byte-identical to
upstream/main there, and our fork-only CLAUDE.md section still survives — so the merge
does not resolve this.
Related: #35 (follow-ups from the same sync).
What happened
Observed in asktt1770/nix-hermes-agent, session of 2026-09-08 (darwin, Claude Code / Opus 5).
An assistant session produced 8 commits, one merge, and two pull requests while working
directly in the main worktree, and edited main in place before being asked to move to a
topic branch. The stated expectation, once it surfaced, was that implementation work happens in
a linked worktree by default.
Why the assistant did not create one
This is a conflict between the documented default and the intended default — not a lapse in
following instructions. Two fragments load into every session and both say the main worktree is
a valid place to work:
agents/shared/git-worktrees.md:9-10
- Use the `git-wt` skill only when a worktree lifecycle operation is actually
needed
agents/skills/git-wt/SKILL.md:21-27
Different paths mean the current directory is already inside a linked
worktree. Continue in that worktree unless the user explicitly requests a
different one. Do not create or select another worktree merely to satisfy an
isolation convention.
Equal paths mean the current directory is the main worktree. Create or select
a linked worktree only when the task or user actually requires isolation.
The detection step ran correctly and returned "this is the main worktree"; the rule then said
that was acceptable. Any remedy consisting of restating the expectation in prose will be read
alongside these two fragments, which contradict it.
Upstream context — this shapes the remedy
Both files are byte-identical to upstream/main; there is currently zero fork divergence in
either:
$ git diff upstream/main -- agents/shared/git-worktrees.md agents/skills/git-wt/SKILL.md
$ # no output
The wording is deliberate upstream, not an accident:
830d019c fix(agents): avoid redundant worktree setup (2026-06-20)
Compare the Git directory and common directory before invoking git-wt.
This identifies an existing linked worktree before isolation begins.
Keep agents there unless the user explicitly requests a lifecycle
operation. This prevents redundant selection in Codex Desktop and
similar environments.
Upstream is solving the mirror image of our problem. Their agents start inside a linked
worktree (Codex Desktop and similar), so an instruction to create one for isolation produced
redundant nesting. Our sessions start in the main worktree. The same text, through the same
detection logic, reaches the opposite conclusion. The text is correct for upstream and wrong for
us, so it cannot be fixed by reporting it upstream.
Two further facts:
- Upstream has no worktree enforcement at all. No hooks in
nix/; upstream's repo-level
.claude/settings.json contains only a nix run .#fmt PostToolUse hook. The rule is
instruction-only everywhere.
- Upstream's root
CLAUDE.md has no "Worktree Workflow" section. Ours (CLAUDE.md:103) is a
fork-only addition and is the single place the intended default is written down. It wins here
and loses in every repository that does not repeat it — which is how nix-hermes-agent
behaved as it did.
Open decisions
Left open deliberately; to be settled with the post-merge files in front of us.
- Channel. Editing
agents/shared/ or agents/skills/git-wt/ reintroduces divergence in
files that are currently clean, against the "keep the upstream diff minimal" policy in
CLAUDE.md, and upstream will keep touching them. Alternatives: a fork-only file under
claude/rules/ (loaded globally via ~/.config/claude/rules/, zero upstream conflict
surface), or leaving it per-repository in .claude/.
- Precedence. A fork-only rules file loads alongside the shared fragments, not after them
in any guaranteed order, so it has to override them explicitly in its wording rather than
relying on position.
- Reach.
claude/rules/ is Claude Code only. ~/.codex/AGENTS.md is generated from
agents/shared/ alone, so anything meant to reach Codex needs codex/AGENTS.md too.
- Mechanism.
EnterWorktree (Claude Code harness tool, used by our CLAUDE.md) vs git wt
(used by the shared fragment). EnterWorktree cannot live in agents/shared/ because Codex
has no such tool.
- Instruction vs enforcement. If a hook is added, where it blocks matters: at edit time
(Edit/Write) it catches everything but fires on legitimate main-worktree work; at commit/PR
time it guards only the irreversible step. Note this repo explicitly permits committing
directly to main, so any mechanical block needs a per-repository opt-out.
- Escape hatch strength. Somewhere the assistant can write (shows up in a diff, reviewable)
vs somewhere it cannot (an env var set when launching claude). A marker file would repeat the
weakness tracked separately in the codex-review gate issue.
- Rebuild boundary.
agents/shared/ and claude/rules/ are out-of-store symlinks and can be
iterated on live; hooks in nix/modules/home/programs/claude-code/default.nix need a rebuild.
Secondary observation
git-wt itself worked without incident once invoked. git wt --nocd <branch> created
.wt/<branch>/ inside the repo, and that directory does not appear in git status in the main
tree, so no .gitignore entry was needed. wt.remover is set to trash-cli; wt.basedir is
unset, so worktrees land under the repository.
Reproducing
# the two fragments that set the default
$ bat agents/shared/git-worktrees.md
$ sed -n '20,30p' agents/skills/git-wt/SKILL.md
# what the detection step returns in a main worktree
$ git rev-parse --path-format=absolute --git-dir
$ git rev-parse --path-format=absolute --git-common-dir # equal => main worktree
# confirm the files are still pristine upstream
$ git diff upstream/main -- agents/shared/git-worktrees.md agents/skills/git-wt/SKILL.md
Gated on #34. Re-check once the upstream sync merges. Verified against the #34 tree
(
origin/chore/upstream-sync-2026-08-14): both files below are still byte-identical toupstream/mainthere, and our fork-onlyCLAUDE.mdsection still survives — so the mergedoes not resolve this.
Related: #35 (follow-ups from the same sync).
What happened
Observed in
asktt1770/nix-hermes-agent, session of 2026-09-08 (darwin, Claude Code / Opus 5).An assistant session produced 8 commits, one merge, and two pull requests while working
directly in the main worktree, and edited
mainin place before being asked to move to atopic branch. The stated expectation, once it surfaced, was that implementation work happens in
a linked worktree by default.
Why the assistant did not create one
This is a conflict between the documented default and the intended default — not a lapse in
following instructions. Two fragments load into every session and both say the main worktree is
a valid place to work:
agents/shared/git-worktrees.md:9-10agents/skills/git-wt/SKILL.md:21-27The detection step ran correctly and returned "this is the main worktree"; the rule then said
that was acceptable. Any remedy consisting of restating the expectation in prose will be read
alongside these two fragments, which contradict it.
Upstream context — this shapes the remedy
Both files are byte-identical to
upstream/main; there is currently zero fork divergence ineither:
The wording is deliberate upstream, not an accident:
Upstream is solving the mirror image of our problem. Their agents start inside a linked
worktree (Codex Desktop and similar), so an instruction to create one for isolation produced
redundant nesting. Our sessions start in the main worktree. The same text, through the same
detection logic, reaches the opposite conclusion. The text is correct for upstream and wrong for
us, so it cannot be fixed by reporting it upstream.
Two further facts:
nix/; upstream's repo-level.claude/settings.jsoncontains only anix run .#fmtPostToolUse hook. The rule isinstruction-only everywhere.
CLAUDE.mdhas no "Worktree Workflow" section. Ours (CLAUDE.md:103) is afork-only addition and is the single place the intended default is written down. It wins here
and loses in every repository that does not repeat it — which is how
nix-hermes-agentbehaved as it did.
Open decisions
Left open deliberately; to be settled with the post-merge files in front of us.
agents/shared/oragents/skills/git-wt/reintroduces divergence infiles that are currently clean, against the "keep the upstream diff minimal" policy in
CLAUDE.md, and upstream will keep touching them. Alternatives: a fork-only file underclaude/rules/(loaded globally via~/.config/claude/rules/, zero upstream conflictsurface), or leaving it per-repository in
.claude/.in any guaranteed order, so it has to override them explicitly in its wording rather than
relying on position.
claude/rules/is Claude Code only.~/.codex/AGENTS.mdis generated fromagents/shared/alone, so anything meant to reach Codex needscodex/AGENTS.mdtoo.EnterWorktree(Claude Code harness tool, used by ourCLAUDE.md) vsgit wt(used by the shared fragment).
EnterWorktreecannot live inagents/shared/because Codexhas no such tool.
(
Edit/Write) it catches everything but fires on legitimate main-worktree work; at commit/PRtime it guards only the irreversible step. Note this repo explicitly permits committing
directly to
main, so any mechanical block needs a per-repository opt-out.vs somewhere it cannot (an env var set when launching
claude). A marker file would repeat theweakness tracked separately in the codex-review gate issue.
agents/shared/andclaude/rules/are out-of-store symlinks and can beiterated on live; hooks in
nix/modules/home/programs/claude-code/default.nixneed a rebuild.Secondary observation
git-wtitself worked without incident once invoked.git wt --nocd <branch>created.wt/<branch>/inside the repo, and that directory does not appear ingit statusin the maintree, so no
.gitignoreentry was needed.wt.removeris set totrash-cli;wt.basedirisunset, so worktrees land under the repository.
Reproducing