Claude Codeのスキルを別のリポジトリから実行する — シンボリックリンク、cd -P、そしてすべてのパスを絶対パスに

Claude Codeのスキルをどのリポジトリからでも呼べるようにする方法。発見にはシンボリックリンク、アクセスにはadditionalDirectories、入口で1回のcd -P、そして相対パスを使わないこと。

白いカードにテラコッタ色のClaudeの星形ロゴと黒のロゴタイプ
目次

はじめに

私は3言語のブログを運営していて、その記事パイプラインはブログ自身のリポジトリに置いたClaude Codeのスキルです。問題は、ほとんどの記事がどこか別の場所で起きた作業について書かれていることでした。別のコードベースでの出来事、修正にたどり着くまでに試して駄目だった3つのこと、誰が何を決めたか。その材料はそれを経験したセッションの中にしか存在せず、1週間経つと痩せた形でしか戻ってきません。

つまり執筆は別のリポジトリで始まってほしい一方で、パイプラインはこちらに残ります。私の回避策はスキルのパスをそのセッションに貼り付けることでしたが、それが生むのは、完成しているように見えて、パイプラインのチェックを1つも通っていない下書きでした。

解決は4つの組み合わせで、しかもどれも私が探しに行ったものではありませんでした。発見のためのシンボリックリンク、アクセスのための permissions.additionalDirectories、入口での1回の cd -P、そしてどこにも相対パスを書かないことです。ここに至るまで1日で4回のリリースを要しました。パスの解決に関する3つの思い込みが別々に間違っており、そのどれもが遠隔側からしか見えなかったからです。

本記事では、まず動く設定を示し、それから思い込みごとに何がそれを覆したのかを整理します。

動く設定

シンボリックリンク2本と設定キー1つ、最初に1回だけです。

ターミナルウィンドウ
mkdir -p ~/.claude/skills
ln -s /path/to/blog-repo/.claude/skills/blog ~/.claude/skills/blog
ln -s /path/to/blog-repo/.claude/skills/svg ~/.claude/skills/svg
~/.claude/settings.json
{
"permissions": {
"additionalDirectories": ["/path/to/blog-repo"]
}
}

これで /blog new <topic> がどのリポジトリからでも動き、セッションごとのコマンドは不要になります。

この2つは別々の仕事をしていて、どちらも他方の代わりにはなりません。シンボリックリンクはスキルを発見可能にします。ドキュメントには、personal・project・enterpriseのいずれの場所でもスキル名のエントリはシンボリックリンクでよく、Claude Codeはリンク先から SKILL.md を読むと書かれています。設定キーのほうはリポジトリへのファイルアクセスを与え、さらにそのディレクトリを許可済みにします。これが効いてくる理由は後述します。

シンボリックリンクはコピーではない、というのが要点です。リポジトリ自身のファイルに解決されるので、ずれようがありません。これが効くのは、personalスキルが同名のprojectスキルを上書きするからです。コピーを置けばブログのリポジトリの中でも本物のパイプラインを覆い隠し、気づかないまま古びていきます。リンクなら、覆い隠す相手が自分自身です。

2本目のリンクは忘れやすいところです。blogスキルは図版の作成を隣の svg スキルに Skill ツール経由で任せており、発見されていないスキルは呼び出せません。

実際の使い心地

呼び出し自体は退屈で、それが狙いです。記事の題材になっている作業の途中、無関係なリポジトリのセッションで次のように打ちます。

/blog new astro service worker cache eviction

スキルが読み込まれ、その入口ステップがブログのリポジトリのパスを表示し、インタビューが始まります。呼び出し側のリポジトリの話は出てきませんし、手でパスを打つこともありません。

正確に書いておく価値があるのは成果物がどこに置かれるかです。ここには間違った答えが用意されているからです。記事のフォルダ、.mdx、図版コンポーネント、画像は、すべてブログのリポジトリに書き込まれます。遠隔側のリポジトリは触られません。材料を抱えたセッションはその場に留まり、渡っていくのはファイルだけです。

記事ファイルはブログのリポジトリへ渡り、セッションの記録は残る2 つのリポジトリが左右に並ぶ。左は作業していたリポジトリで、セッションの記録を持ち、new と figures のサブコマンドがここで動く。矢印が記事ファイルを持って右へ渡る。右はブログのリポジトリで、記事とビルドを持ち、publish がここで動く。セッションの記録自体は動かないと注記される。作業した場所で書き、サイトをビルドする場所で公開する作業していたリポジトリセッションの記録がある/blog new/blog figuresブログのリポジトリ記事とビルドがある/blog publish記事ファイルセッションの記録は動きません。だから執筆のほうを寄せます。
渡るのは記事ファイルだけです。記事の材料であるセッションの記録は動きません。だからこそ材料を受け渡すのではなく、執筆のほうがそこへ寄っていきます。

そのあと、ブログのリポジトリに戻って次を実行します。

/blog publish astro-service-worker-cache-eviction

公開だけは手元に残りますが、それは整理整頓のためではありません。図版のラベルを測るために開発サーバーを起動し、本番ビルドを走らせ、ビルド出力をgrepします。この3つを支配するルールはリポジトリ自身の CLAUDE.md にあり、CLAUDE.md はシンボリックリンク経由でも --add-dir 経由でも読み込まれません。遠隔セッションでこれらを実行すると、ガードレールが外れた状態になります。

サブコマンドは1回のcdから始まる

--add-dir もシンボリックリンクも、スキルには到達させてくれます。しかしどちらも作業ディレクトリを変えないので、そこは遠隔側のリポジトリのままです。つまり pnpm check も node scripts/prose-check.ts も、スキルの中の他のあらゆるコマンドも、間違ったプロジェクトに対して走ることになります。

Bashツールは1つのシェルを保持し、その作業ディレクトリは呼び出しをまたいで持続します。ですからサブコマンドの先頭で1回 cd すれば、スキル内の35個のコマンドブロック全部に効きます。

ターミナルウィンドウ
cd -P ${CLAUDE_SKILL_DIR}/../../.. && pwd

pwd は飾りではありません。「今いるのは違うリポジトリだ」を黙って見過ごさずに見えるようにするためのもので、遠隔側のリポジトリがたまたま自前の check スクリプトを持っていれば、これがないと間違ったプロジェクトに対してチェックが通ってしまいます。

この持続には知っておくべき条件が1つあり、それがこの設計の成立可否を決めます。3つの対象で測った結果です。

cd の対象許可済みか次の呼び出しの pwd
プロジェクトルート内のディレクトリはい保持された
additionalDirectories に載せた /tmpはい保持された
一覧にないリポジトリいいえShell cwd was reset to …

作業ディレクトリが生き残るのは、そのディレクトリが許可済みである間だけです。だから additionalDirectories は任意ではありません。これがないと cd はコマンドのあと静かに取り消され、次のチェックは遠隔側のリポジトリで走ります。

思い込み1: 複合コマンドを事前承認できる

最初の版では、入口ステップをスキルの allowed-tools のルールで事前承認していました。

Bash(cd ${CLAUDE_SKILL_DIR}/../../.. && pwd)

このルールは決してマッチしません。権限のドキュメントは明示しています。認識される区切りは &&・||・;・|・|&・& と改行であり、ルールはサブコマンドごとに独立してマッチしなければならない、と。自身のテキストに && を含むルールは、そのために書かれたコマンドのどちらの半分にもマッチしません。

このルールは何も与えておらず、しかもそれが静かでした。それでもステップがプロンプトなしで走ったのは、許可済みディレクトリへの cd と pwd がどちらも組み込みの読み取り専用コマンドだからです。ルールは分割ではなく削除しました。あのステップでのプロンプトは残す価値のある信号だからです。

その信号が何を意味するのかは、2度目の訂正でようやく正しくなりました。最初の版では、あそこでのプロンプトは対象が許可済みでないことを意味すると書いていました。実際にはもう1つ、その文脈でBashツールが許可されていないという意味にもなります。隣の svg スキルに起きたのがまさにこれで、allowed-tools に Bash の項目が1つもない状態で cd を使う必須ステップを追加していました。原因は2つ、直し方も2つで、片方だけを挙げると読み手を間違ったファイルへ送ります。

同じルールが2つ目の主張も崩した

同じ分割ルールが2つ目の主張も崩しました。私は以前、すべてのコマンドに cd … && を前置する設計を却下していて、エージェントはその撤回を「前置すると既存の素の Bash(pnpm check) ルールが壊れるから」と正当化していました。壊れません。サブコマンド単位のマッチングにより、cd X && pnpm check は pnpm check のルールに問題なくマッチします。

私が前置を却下した理由は、稀なケースのために手数が増えるからでした。その理由は今も有効です。その下に書き添えられていた理由のほうが違いました。

思い込み2: /add-dirが使える

/add-dir <path> は、シンボリックリンクと設定キーが合わせてやることを1コマンドで済ませます。最初のリリースでは、これが唯一の文書化された入口でした。

これはCLI専用です。VS CodeとJetBrainsの拡張には存在しません。そして私が実際に書いているのはそこです。この機能は、それが作られた部屋からは開けられない玄関を持って出荷されたことになります。

ドキュメントにその断りはありません。権限のページは「--add-dir フラグまたは /add-dir コマンド」と書くだけで例外の記載はなく、拡張固有の差分としてどこかに記録されているのは /bug だけです。Issue #36123 はIDE拡張向けにこれを要望していましたが、判断としてではなく、放置による自動処理で NOT_PLANNED として閉じられました。これはより広い既知の穴の一部でもあります。拡張のスラッシュコマンド一覧はCLIに比べて不完全であることが報告されています。

明らかな回避策が効かない理由

統合ターミナルで claude --add-dir <repo> を走らせれば、本物のフラグと本物の置換で問題なく動きます。しかしそれは新しいセッションを始めることになり、書く価値のある材料は今のセッションの記録の中にあります。その記録こそが、執筆を別の場所で始める理由そのものです。

/add-dir は2番目の経路として文書に残してあります。使える環境ではこちらのほうが優れているからです。追加したリポジトリの .claude/skills/ ディレクトリ全体を、隣のスキルも含めて読み込み、同じ動作でリポジトリを許可済みにします。シンボリックリンクも設定キーも要りません。

思い込み3: シンボリックリンクは透明である

これが2回のリリースを費やしたもので、持ち帰る価値があるのはこの部分です。

シンボリックリンクされたディレクトリは下りには透明、上りには不透明です。その中へ入る読み取りは実体のファイルに解決されます。.. でそこから出るものは解決されません。パスが字句的に正規化されるからです。.. はテキスト上のパスに対して畳まれ、リンク先を見ることがありません。

シンボリックリンクの内側への読み取りは解決し、.. での脱出は解決しない2 行の比較。1 行目は、シンボリックリンクされたスキルディレクトリへ下るパスがリポジトリ内の実ファイルに到達し、解決すると示される。2 行目は、同じディレクトリに 3 段の .. を続けたパスがリポジトリではなくホームディレクトリ配下に到達し、見つからないと示される。シンボリックリンクは下りには透明、上りには不透明書いたパス実際に到達する先~/.claude/skills/blog/voice.md<repo>/.claude/skills/blog/voice.md解決するリンク先ディレクトリへ下る読み取りはリポジトリに届きます。~/.claude/skills/blog/../../../docs/glossary.md~/docs/glossary.md見つからない.. はリンク自身の親をたどるため、何段重ねてもリポジトリには届きません。
リンク先ディレクトリへ入る読み取りはリポジトリに届きます。.. はどれもリンク自身の親をたどり、ホームディレクトリに着きます。

~/.claude/skills/blog にスキルをリンクした状態で測った結果です。

パス結果
<link>/voice.md実体のファイルに解決される
cd <link>/../../..誤り、~ に着く
cd -P <link>/../../..正しい。シンボリックリンクがない場合と同じ結果
<link>/../../../docs/glossary.md の読み取りFile does not exist
<link>/../svg/web-figure.md の読み取りFile does not exist。隣のスキルでも同じ

ここから2つの帰結が出ます。

cd には -P が要ります。シェルは論理的な作業ディレクトリを保つので、素の cd は ../../.. をシンボリックリンクのパス上でたどります。-P はリンクを先に物理的に解決し、そのうえで実体の位置から .. を適用します。シンボリックリンクがない場合も同じ結果になるので、無条件にしても損はありません。

ファイル系のツールに -P はなく、.. を何段重ねてもリポジトリには届きません。${CLAUDE_SKILL_DIR} はリンク自身のパスに展開されるので、これを使って外へ出ることもできません。スキルのディレクトリの外にあるものは、入口の cd が表示したルートから組み立てる必要があります。だからこのステップは pwd を報告し、スキルはそれ以降その値を名前で参照します。

思い込みの中の思い込み

ここまで書き下したうえで、スキルはなお自分自身のファイルへの相対パスが動くと主張しており、しかもその主張に「推測ではなく実測」というラベルを貼っていました。

実測されていませんでした。テストされていたのは ~/.claude/skills/blog/voice.md、つまり絶対パスの形です。スキルが実際に書いていたのは [voice.md](voice.md)、つまり相対パスの形でした。1語違いの別々の主張で、「実測」の語がついていたのは間違ったほうでした。

無関係なリポジトリからの診断実行は、11個の合格と3個の失敗を返しました。その3つは、パイプラインが自分の文章規約を読むところでした。

File does not exist. Note: your current working directory is
/Users/…/oharu-tech-blog.

ファイル系のツールは、相対パスをスキルのディレクトリを基準には解決しません。読み込んでいるファイルの真横にあるファイルであってもです。

修正は機械的でした。10ファイルにまたがる61本のマークダウンリンクを、ディレクトリを含んだ形に書き換えました。これでレンダリング後のディスパッチ表は [new.md](/Users/…/.claude/skills/blog/new.md) となり、そのまま渡すのが正解になります。

規約として書くだけでもおそらく動いたはずです。遠隔セッションは、それを使ったすべてのチェックで同種の規約を正しく適用していました。それでもリンクを書き換えたのは、1つの非対称性のためです。プレースホルダーは未解決に見え、無視した読み手には明らかに壊れたパスが返ります。一方 [voice.md](voice.md) は、すでに動いているリンクに見えます。正しく読め、正しくレンダリングされ、使う瞬間にだけ失敗します。2回のコードレビューと1回のリリースを生き延びたのはそのためです。

いずれの誤りも、作業の外側から覆された思い込みと、それを覆した出来事を並べた 3 行。/add-dir がどのクライアントでも使えるという思い込みは、VS Code 拡張で実際に入力したことで覆された。権限ルールに && を含められるという思い込みは、権限のドキュメントを読んだことで覆された。相対リンクがスキルのディレクトリを基準に解決するという思い込みは、無関係なリポジトリからの診断実行で覆された。いずれも作業自体を読み返して見つかったものではない。3 つの思い込みと、それを覆したもの思い込み覆したもの1/add-dir はどのクライアントでも使える入口であるVS Code 拡張で実際に入力してみた2allowed-tools のルールに&& を含められる権限のドキュメントを読んだ3相対リンクはスキルのディレクトリを基準に解決する無関係なリポジトリからの診断実行どれ 1 つとして、読み返して見つかったものはありません。
各リリースが前のリリースの思い込みを直しました。それを覆したものは毎回、作業の外側から来ています。エディタ、ドキュメント、そして別のリポジトリでの実行です。

別の場所でしか壊れないものをどうテストするか

遠隔側と手元側があるものなら他でも使い回すつもりの部分で、ここにたどり着くまでに3回、間違った訊き方をしました。

問題は構造的です。バグは向こうのリポジトリにしかなく、修正はこちらにしかなく、その2つは互いを見られない別々のセッションです。「そちらで動きますか」と訊くと散文で返ってきます。これは手に入る形の中で最も使えないものです。観測したことと推測したことが混ざり、向こうのセッションが言及しようと思わなかったものは落ちます。

うまくいくのは、番号付きのチェック群と固定の出力書式を1つのプロンプトに入れて送り、その表を返してもらうやり方です。おおよそ次の形です。

Run a diagnostic. This is a TEST: change nothing, write nothing.
T1 Report the base directory the skill loaded from.
T2 Report the exact path the entry step printed.
T3 In a FRESH shell call, run pwd. Report it verbatim.
T4 Read voice.md via the link as the skill writes it. First line?
…
T14 NEGATIVE CONTROL. This is EXPECTED TO FAIL. Try to read exactly:
~/.claude/skills/blog/../../../docs/glossary.md
If it SUCCEEDS, say so loudly.
OUTPUT — reply with only this:
| ID | PASS/FAIL | Evidence (verbatim, one line) |
ANY PATH THAT FAILED TO RESOLVE
ANYTHING YOU COULD NOT DO

この形は最初の試みではありませんでした。1回目は遠隔セッションにサブコマンドを2つ走らせて何が起きたか説明してもらうもので、まさに物事を覆い隠す読みやすい散文が返ってきました。私はそれを、固定の出力書式を持つ1つのプロンプトに組み直すよう求め、残す価値があるのはその版です。

このチェック群が効く理由

4つあり、どれも先に抜かしてみて学びました。

  • 判定ではなく逐語的な証拠を要求する。PASS と書かれた列は主張ですが、# Voice が入っている列は事実です。表を読む人がその場にいなかったとき、本物の合格ともっともらしい合格を分けるのが証拠の列です。
  • ネガティブコントロールを入れる。失敗するはずのチェックこそが、その仕掛けが正直であることを示します。失敗すべきものまで含めてすべて合格するなら、その実行は何も測っていません。
  • できなかったことを訊く。これがこの作業で最も有用な1行を生み、しかも求めずに出てきました。遠隔セッションは、ネガティブコントロールのエラーが3つの本物の失敗と1バイトも違わないため、あの実行だけでは競合する2つの説明を区別できないと報告してきたのです。この1行がなければ、その結果はテストが実際には獲得していない証拠として数えられていたはずです。
  • 「何も直すな」と書く。パスを直して静かにやり直すセッションは成功を報告し、証拠を壊します。静かな復旧こそ、最初の誤った主張が生き延びた経路そのものです。

以降のループは、向こうでチェック群を走らせ、表をこちらに貼り、こちらで直し、また走らせる、という形になります。遠隔セッションは何も編集しません。これにより、変更が起きる場所を1つのリポジトリに限れます。

できないことも知っておく価値があります。古い挙動に対して書いたプロンプトは、挙動が変わった瞬間に古びます。リンクを絶対パスにしたあと、3つのチェックはなお「スキルが書いている相対リンク」を求めており、それはもう存在しないので、何もテストしないまま失敗しました。遠隔セッションはそれにも気づき、どちらか一方を選ぶのではなく両方の読み方を試していました。

実際に何がこれらを捕まえたか

残りの作業をどう検証するかを決めた話なので、はっきり書いておきます。

2回のコードレビューと1回のリリースが、相対パスの主張を素通りさせました。別のリポジトリからの14項目の診断実行は、それを一発で見つけました。レビューは2つのテキストの間の矛盾を見つけるのは得意ですが、それ自体は筋が通っていて世界について間違っている文を捕まえることはできません。

残りの2つも同じ捕まり方をしました。輪の外側からです。/add-dir が無いことは、私が実際に打ってみて分かりました。複合ルールの誤りは、検証役のエージェントがコードではなく権限のドキュメントを読んだときに初めて浮かびました。

あの診断の限界

ネガティブコントロールが返したエラーは3つの本物の失敗と1バイトも違わず、あの実行だけでは「ディレクトリの外へ出て畳まれる」と「相対パスはそもそも基準を持たない」を区別できませんでした。字句的に畳まれるというモデルが生き残っているのは別の証拠によるもので、それは絶対パスで採ったため作業ディレクトリに依存しません。2つの説明を区別できないテストは、判定欄に何と書いてあろうと、その2つの間で選んではいません。

1つ、意図的に開いたままの問いがあります。相対パスが何を基準に解決されるのか、セッションのディレクトリなのかシェルのディレクトリなのかは、両者を区別できるパスでテストしたことがなく、スキルはどちらかを選ばずにそう書いてあります。絶対パスならどちらでも正しいので、答えに依存するものはありません。議論で決着させるのは、先の3つが間違った経路そのものです。

まとめ

Claude Codeのスキルをどのリポジトリからでも実行できるようにするには、次のようにします。

  • スキルのディレクトリを ~/.claude/skills/ にシンボリックリンクする。呼び出す隣のスキルもすべてです。リンクは古びませんが、コピーは元を覆い隠したうえで古びます。
  • リポジトリを permissions.additionalDirectories に加える。ファイルアクセスを与え、そのディレクトリを許可済みにします。これがないと作業ディレクトリは呼び出しをまたいで静かに保持されなくなります。
  • 各サブコマンドを cd -P ${CLAUDE_SKILL_DIR}/../../.. && pwd で始め、そのパスを報告する。1回の cd でスキル内のすべてのシェルコマンドが賄え、シンボリックリンク越しでも正しくするのが -P です。
  • 相対パスのファイルパスを一切使わない。同じディレクトリのファイルに対してもです。リンクはディレクトリを含んだ形で書き、スキルの外にあるものは入口ステップが表示したパスから組み立てます。
  • /add-dir は最初の2ステップを1コマンドで置き換えますが、IDE拡張には存在しません。使える環境では使ってください。
  • 番号付きのチェック群と固定の出力書式でテストする。実際の遠隔セッションから走らせ、逐語的な証拠、ネガティブコントロール1つ、そして「できなかったこと」の明示を求めます。

繰り返し現れた失敗は、どれか1つのパスが間違っていたことではありませんでした。隣接する何かをテストしたうえで仕組みを書き下していたことです。相対パスではなく絶対パスを、遠隔セッションではなく手元のセッションを。そのたびに訂正をもたらしたのは、もう一度読み直すことではなく、別の場所で実際に動かすことでした。

参考リンク

この記事をシェア