ghq環境でorg別にAGENTS.mdを配ったら @ 参照でハマった

概要

  • ghqで ~/ghq/github.com/<org>/ 配下にリポジトリを並べている
  • orgごとにClaude Code向けの共通指示を持たせたくて、<org>/CLAUDE.md を置いていた
  • あとから AGENTS.md にも対応させようとしたら、CLAUDE.md 側の @AGENTS.md 参照が静かに解決失敗していた
  • 原因はchezmoiで貼っているsymlinkと、Claude Codeの @ パス解決の相性

前提: ghqのorg別ディレクトリ構成

ghqを使うと、リポジトリは ~/ghq/github.com/<org>/<repo>/ に整列する。
<org> ディレクトリはただの入れ物なので、ここに共通ファイルを置いておくと便利。

~/ghq/github.com/
├── myorg/
│   ├── CLAUDE.md          # myorg 全体の共通指示
│   ├── repo-a/
│   └── repo-b/
└── workorg/
    ├── CLAUDE.md          # workorg 全体の共通指示
    ├── repo-x/
    └── repo-y/

Claude Codeはcwdから親方向に CLAUDE.md を探すので、~/ghq/github.com/myorg/repo-a/ で起動したセッションは myorg/CLAUDE.md を自動で読んでくれる。
org単位で「このリポジトリ群は何のためのものか」「共通で守ってほしいルール」を渡せるのはかなり良い。

chezmoiでsymlink配布する構成

<org>/CLAUDE.md の実体はdotfilesリポジトリの中に持っておきたい。
そこでchezmoi側にファイルを集め、.chezmoiscripts/ からsymlinkを張る形にした。

~/ghq/github.com/myorg/dotfiles/
└── org-agent-md/
    ├── myorg-claude.md
    └── workorg-claude.md

chezmoiのonchangeスクリプトが、この <org>-claude.md~/ghq/github.com/<org>/CLAUDE.md にsymlinkで貼る。

ln -sfn org-agent-md/myorg-claude.md ~/ghq/github.com/myorg/CLAUDE.md

これでdotfilesをどのマシンに配っても、org別 CLAUDE.md が自動で並ぶ。

AGENTS.mdを追加したくなった

Claude Codeに加えて他のエージェント (Codex等) も同居させたくなり、AGENTS.md を追加することにした。
共通の指示は AGENTS.md に寄せて、CLAUDE.md からは @AGENTS.md の1行で参照させれば重複が消えて楽になる。

構成をこう拡張した。

~/ghq/github.com/myorg/dotfiles/
└── org-agent-md/
    ├── myorg-agents.md
    ├── myorg-claude.md         # 中身は「@AGENTS.md」の 1 行
    ├── workorg-agents.md
    └── workorg-claude.md       # 中身は「@AGENTS.md」の 1 行

symlinkスクリプトも -agents.md 側を扱うように拡張し、chezmoi applyで両方張るようにした。

ln -sfn org-agent-md/myorg-agents.md ~/ghq/github.com/myorg/AGENTS.md
ln -sfn org-agent-md/myorg-claude.md ~/ghq/github.com/myorg/CLAUDE.md

これで ~/ghq/github.com/myorg/repo-a/ でClaude Codeを起動すれば、CLAUDE.md@AGENTS.md の連鎖でorg共通指示が読み込まれる、はずだった。

ハマったポイント

しばらく作業していて、Claude Codeがorg共通指示に沿った挙動をしていないことに気付いた。
確認したら、AGENTS.md の中身がまったく参照されていなかった。

原因は @AGENTS.md の解決先。

  • Claude Codeは CLAUDE.md から @AGENTS.md を辿るとき、実ファイルのあるディレクトリを基準に解決する
  • 今回の CLAUDE.md の実体は dotfiles/org-agent-md/myorg-claude.md
  • なので @AGENTS.mddotfiles/org-agent-md/AGENTS.md を探しに行く
  • そのディレクトリに置いてあるのは myorg-agents.md であって AGENTS.md ではない
  • ファイルが無いので静かに解決失敗する

エラーや警告は一切出ないため、Claude Codeの挙動を見ながら「なんか薄いな」と気付くまで時間がかかった。

~/ghq/github.com/myorg/CLAUDE.md            (symlink)
dotfiles/org-agent-md/myorg-claude.md       (実体)
                    │  「@AGENTS.md」
dotfiles/org-agent-md/AGENTS.md             ← 無い!
                                              (あるのは myorg-agents.md)

~/.claude/CLAUDE.md の側は同ディレクトリに実体の AGENTS.md を置いているので問題が起きず、org側だけで再現する構図になっていた。

対策

symlink構成を維持したまま直すなら、<org>-claude.md の中で参照名を実ファイル名に合わせるのが一番小さい。

- @AGENTS.md
+ @myorg-agents.md

これで dotfiles/org-agent-md/myorg-agents.md が確実に読まれる。
orgが増えても <org>-claude.md の中身を @<org>-agents.md に揃えるだけで済むので、chezmoiの運用ともかみ合う。

別解として dotfiles/org-agent-md/ の中にリンク解決用の AGENTS.md をorgごとに別ディレクトリで持つ手もある。
とはいえorg別に実体ディレクトリを増やす分だけ構成が重くなるので、今回は不採用にした。

感想

  • 「symlinkの中身が同じ内容なら見え方も同じ」という素朴な期待は成立しない
  • 相対参照は実ファイル基準で解決される
  • Claude Code側は @ の解決失敗を静かに握りつぶすので、参照が効いているかは実挙動で確認するしかなかった
  • chezmoiのように「実体は別ディレクトリ、公開先はsymlink」という配布形態を取るときは、実体側で相対参照が閉じるようにファイル名を設計しておく必要がある
  • ghqのorg別ディレクトリを「共通指示の置き場」として使うアイデア自体はかなり便利なので、この落とし穴を踏まずに広めていきたい
Built with Hugo
Theme Stack designed by Jimmy