Skip to content

fix(install): one payload on dual-target installs, .claude/ symlinked at it - #70

Merged
pgebheim merged 2 commits into
mainfrom
fix/dedupe-dual-target-installs
Sep 11, 2026
Merged

pgebheim merged 2 commits into
mainfrom
fix/dedupe-dual-target-installs

Conversation

@pgebheim

@pgebheim pgebheim commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #69.

Problem

A repo used by Claude Code and Codex/Cursor detects both targets, and each adapter laid down its own full copy:

.claude/skills/rig-review/SKILL.md     .agents/skills/rig-review/SKILL.md
.claude/REVIEWER.md                    .rig/REVIEWER.md
.claude/agents/  .claude/scripts/      .rig/agents/  .rig/scripts/

Two sources of truth, one edit away from drifting, with no signal about which side is canonical.

Change

Claude Code can't be pointed at .agents/skills/ — no settings key, no env var, and --add-dir only picks up .claude/skills/ inside the added dir. But it does follow symlinks for skill directories and plain files.

So on a dual-target install, keep one physical payload in .agents/ + .rig/ (the locations other agents discover natively) and symlink .claude/ at it:

link target
.claude/skills/<name> ../../.agents/skills/<name>
.claude/agents/<file> ../../.rig/agents/<file>
.claude/scripts/<file> ../../.rig/scripts/<file>
.claude/REVIEWER.md ../.rig/REVIEWER.md (same for label-mapping.md)

Link text is relative to each link's own directory, so links survive a clone or a moved checkout — git stores them as mode 120000, not as copies.

Guardrails

  • Single-target installs are untouched. claude-code alone still writes a real .claude/ tree and never creates .agents/; agents-md alone never creates .claude/. Dedup engages only when both targets resolve.
  • Probed, not assumed. It tests that symlinks actually work first; on Windows without developer mode it prints a note and falls back to copying.
  • Ordering forced. Both adapters now run on one install and claude-code links at what agents-md lays down, so agents-md runs first regardless of the order targets were given.

Side benefit

review.patternsFile is a single config key that had two candidate REVIEWER.md paths, so one was always the dead copy. They now resolve to the same file.

Verification

  • Dual install → 2 physical SKILL.md, 17 symlinks, all resolving, none dangling
  • claude-only and agents-only → zero symlinks, same trees as before
  • --target claude-code,agents-md given in that order → still links correctly
  • Re-run idempotent → 35 skips, one rig:start marker pair, still 2 physical SKILL.md
  • git init + commit + git clone round-trip → every link resolves in the fresh clone
  • bun test scripts/ — 39 pass

Adjacent, not fixed here

skills/rig-worktree/SKILL.md hardcodes .claude/scripts/setup-worktree.sh in four places, with no config indirection (unlike the <TRACKER> pattern in rig-epic/rig-task/rig-issue/rig-doctor/rig-plan). The symlinks in this PR make that path work on dual installs, but it's still broken on an agents-md-only repo, where .claude/ doesn't exist at all. Worth its own fix.

🤖 Generated with Claude Code

https://claude.ai/code/session_014oVJgHRkCkeCiayKUJ4iSg

… at it

A repo used by Claude Code *and* Codex/Cursor detects both targets, and each
adapter laid down its own full copy: two `SKILL.md` per skill, two `REVIEWER.md`,
two `agents/`, two `scripts/`. Two sources of truth one edit away from drifting,
and no signal to the reader about which side is canonical.

Claude Code can't be pointed at `.agents/skills/` — there's no settings key or
env var, and `--add-dir` only picks up `.claude/skills/` inside the added dir —
but it does follow symlinks for skill directories and plain files. So on a
dual-target install keep one physical payload in `.agents/` + `.rig/` (the
locations other agents discover natively) and symlink `.claude/` at it:

  .claude/skills/<name>  -> ../../.agents/skills/<name>
  .claude/agents/<file>  -> ../../.rig/agents/<file>
  .claude/scripts/<file> -> ../../.rig/scripts/<file>
  .claude/REVIEWER.md    -> ../.rig/REVIEWER.md   (same for label-mapping.md)

Link text is relative to each link's own directory, so the links survive a clone
or a moved checkout — git stores them as mode 120000, not as copies.

Single-target installs are untouched: claude-code alone still writes a real
`.claude/` tree and never creates `.agents/`; agents-md alone never creates
`.claude/`. Dedup engages only when both targets resolve, and only after probing
that symlinks actually work — on Windows without developer mode it prints a note
and falls back to copying. Since both adapters now run on one install and
claude-code links at what agents-md lays down, agents-md is forced to run first
regardless of the order targets were given.

Side benefit: `review.patternsFile` was a single key with two candidate
REVIEWER.md paths, so one was always dead. They now resolve to the same file.

Verified end-to-end: dual install yields 2 physical SKILL.md and 17 symlinks,
all resolving, none dangling; claude-only and agents-only produce zero symlinks
and the same trees as before; `--target claude-code,agents-md` given in that
order still links correctly; re-run is idempotent (35 skips, one marker pair);
and a git init + clone round-trip resolves every link in the fresh clone.
`bun test scripts/` — 39 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oVJgHRkCkeCiayKUJ4iSg
pgebheim added a commit that referenced this pull request Sep 3, 2026
…arget (#71)

Two paths were hardcoded to `.claude/` with no indirection, so on an
agents-md-only repo — Codex, Cursor, Gemini CLI, the population #69 and #70
exist to serve — they pointed at files that aren't there.

rig-worktree named `.claude/scripts/{setup,remove}-worktree.sh` in four places
while `agents-md` installs those scripts to `.rig/scripts/`, and the repo has no
`.claude/` at all. The skill told the agent to run a path that doesn't exist,
right after insisting it must not reimplement the script inline. It now resolves
`<SCRIPTS>` first-hit-wins over `.claude/scripts/` then `.rig/scripts/`, in the
spirit of the `<TRACKER>` resolver rig-epic/rig-task/rig-issue/rig-doctor/rig-plan
already use, and stops with a clear message if neither has the script. The
scripts themselves already resolved their own siblings, so only the callers
needed fixing.

install.sh copied rig.config.example.json verbatim, so every install got
`review.patternsFile: .claude/REVIEWER.md` and
`tracker.labelMapFile: .claude/label-mapping.md`. Without the claude-code target
those docs were installed under `.rig/`, leaving both keys dangling on a fresh
install. It now rewrites the two paths when claude-code isn't among the targets.
Uses an inline membership test rather than a helper, to stay independent of #70.

`.claude/worktrees/` as the default worktree location is deliberate and
unchanged — the Claude Code harness only adopts worktrees there (see 41283b3).
Documented that it carries no special meaning elsewhere and `--path` overrides.

Verified across all three shapes: claude-only keeps `.claude/` paths and resolves
to `.claude/scripts`; agents-only gets `.rig/` paths and resolves to
`.rig/scripts`; dual keeps `.claude/` paths, both files present. The prescribed
resolver snippet was executed as written against each install, and every
configured doc path was checked to exist. `bun test scripts/` — 39 pass.


Claude-Session: https://claude.ai/code/session_014oVJgHRkCkeCiayKUJ4iSg

Co-authored-by: Paul Gebheim <86010+pgebheim@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@pgebheim
pgebheim merged commit 7538b59 into main Sep 11, 2026
2 checks passed
@pgebheim
pgebheim deleted the fix/dedupe-dual-target-installs branch September 11, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant