
Windows Subsystem for Linux (WSL) で Claude Skills が使えるようになるまでの奮闘記録
コーヒーが好きな emi です。
社内の Claude Code Skills ハンズオンに参加するため、Windows Subsystem for Linux (WSL) 上の Claude Code で /plugin コマンドを使ってプラグインを導入しようとしました。
その過程で、ターミナルの画面が崩壊したり、GitHub へのアクセスが 403 エラーで拒否されたりと、Skills 本編に入るまでにいくつかのトラブルに遭遇しました。
この記事では、Claude Code Skills を使えるようにするまでに実際にぶつかった壁と、その対処(対処できなかったものも含む)を紹介します。
最終的には議事録ファイルから Skills を使って提案書ドラフトを生成し、Marp スライド 43 枚まで自動生成できるところまで確認できました。
なお、Windows 端末の WSL 上に Claude Code をセットアップするまでの手順は本記事では扱いません。以下の記事を参照してください。
つまずき一覧
Claude Code Skills のハンズオンに入る前に、実際にぶつかった 6 つのつまずきです。詳細は本文で順番に紹介しますが、まずは全体像から。
| # | つまずき | #### 症状 | 原因 |
|---|---|---|---|
| 1 | ターミナルが謎の文字列で埋まる「画面崩壊バグ」 | ANSI エスケープの残骸が生文字で表示され TUI が崩壊 | GPG パスフレーズ入力(pinentry)と Claude Code の TUI 描画が端末を奪い合って衝突 |
| 2 | マーケットプレイス追加で 403 エラー | /plugin marketplace add が 403 Write access to repository not granted で失敗 |
準備済みの GitHub Fine-grained Personal Access Token (PAT) が個人アカウント用のみで、組織(Organization)への権限がなかった |
| 3 | GPG パスフレーズキャッシュ切れの再発 | 翌朝 gpg: public key decryption failed で認証エラー |
GPG エージェントのキャッシュ失効(再度パスフレーズを入力し再認証) |
| 4 | Shai-Hulud 対策の古いエイリアスが残っていた | npm --version 実行で「実行しないでください」と警告される |
サプライチェーン攻撃(Shai-Hulud)対策の社内方針に沿って .bashrc に設定していたエイリアスが、方針緩和後に残っていた |
| 5 | 新しいリポジトリで 403 エラー | 別のハンズオン用リポジトリの clone で再び認証エラー | 組織用 GitHub Fine-grained Personal Access Token (PAT) が「1 リポジトリのみ許可」でスコープ外だった |
| 6 | npm が使えるよう設定したが、pnpm が使われる | Marp スキルが pnpm dlx を実行し、npm 環境と噛み合わない |
Claude Code 君が色々考えて pnpm を採用した |
動作環境と Git 認証構成
奮闘の大半は、Claude Code 自体の不具合ではなく、事前に組んでいた独自の Git 認証構成が引き起こしたものでした。まず動作環境と、この後の話で鍵になる Git 認証構成を整理しておきます。
動作環境
| 項目 | バージョン |
|---|---|
| Windows | 10.0.26100.8655 |
| WSL | 2.7.10.0 |
| WSL のカーネル | 6.18.33.2-2 |
| ディストリビューション | Ubuntu 22.04.5 LTS(Jammy Jellyfish) |
| Claude Code | 2.1.199 |
| Git | 2.34.1 |
(wsl.exe --version、/etc/os-release、claude --version、git --version で確認)
Git 認証構成
- WSL(Ubuntu) + Claude Code CLI
- Git の認証には以下の構成を使用
- Git Credential Manager(GCM):Git の認証情報を管理するツール
credentialStore = gpg:GCM が認証情報を GPG で暗号化して保存する設定pass(~/.password-store/):GPG で暗号化されたパスワード(今回は GitHub トークン)を保存する仕組み
- これらは以前別の記事を参考にして「GitHub Fine-grained Personal Access Token (PAT) を使った安全な GitHub 認証」のために構築していたもの
GCM・GPG・pass について
GitHub に CLI からアクセスする際、毎回トークンを入力せずに済ませるには、どこかに安全に保管しておく必要があります。この「トークンをどこにしまうか」を担うのが 3 つのツールです。銀行の貸金庫に例えて整理します。
- 💁 Git Credential Manager(GCM)= 銀行の窓口係
Git 用のトークン保管・管理ツール。Git から「GitHub のトークンが欲しい」と頼まれたときに、保管庫まで取りに行ってくれる窓口係のような存在です。一度認証に成功すると、次回以降は保管しておいたトークンを自動で使ってくれるので、毎回パスワードやトークンを入力しなくて済むようになります。 - 🏦
pass= 金庫室(保管場所)
暗号化されたファイルを~/.password-store/というディレクトリにまとめて保管する、シンプルなパスワード管理ツールです。トークンを含むファイルが並んでいる「金庫室」そのものにあたります。ただし、中に置かれているファイルはすべて暗号化されているので、金庫室に入っただけでは中身を読むことはできません。 - 🔑 GPG (GNU Privacy Guard) = 金庫を開ける鍵
ファイルの暗号化・復号を行うツールです。passに保管されているファイルは GPG で暗号化されているため、中身を取り出すには GPG による復号が必要になります。そして GPG の鍵を使うときには、鍵を開けるための合言葉である パスフレーズ の入力が求められます。
Windows なら「資格情報マネージャー」、Mac なら「キーチェーン」という OS 標準の保管庫があるので GCM はそれを使うだけで済みますが、Linux には標準の保管庫がありません。そのため GPG(鍵)+ pass(保管庫)を自分で組み合わせ、GCM 側で credentialStore = gpg と指定します。この「自分で用意した鍵」が、以降のつまずきで何度も登場します。
💡 余談:なぜ SSH ではなく Personal Access Token (PAT) + GPG?
余談:なぜ SSH ではなく Personal Access Token (PAT) + GPG?
GitHub の認証というと SSH 鍵を思い浮かべる方も多いと思いますが、今回は SSH を使わず、Fine-grained PAT を GPG で暗号化して保管する構成にしています。
きっかけは Shai-Hulud のような npm サプライチェーン攻撃に備えた社内方針で、「SSH 秘密鍵を端末内に置くこと自体をやめる」という指針が出されたためです。SSH 秘密鍵は一度端末上に置くと、そのままではファイルとして読み取り可能な状態になりがちで、攻撃者に端末内部を漁られた場合にそのまま持ち出されるリスクがあります。
代替として採用したのが Fine-grained PAT + GPG 暗号化保管の構成で、PAT は pass の中に GPG で暗号化された状態で置かれるため、ファイルとして抜き取られてもパスフレーズなしには復号できません。加えて Fine-grained PAT はリポジトリ単位・権限単位でスコープを細かく絞れるので、万一漏洩しても被害範囲を最小化できます。
なお、SSH と GPG は「鍵」つながりで混同しがちですが、SSH は認証(身分証)、GPG は暗号化・復号(金庫の鍵)、と役割が違います。
今回の構成では GPG は「Git の認証そのもの」ではなく、暗号化された PAT を復号して取り出すために使われています。
💡 シャイフルード(Shai-Hulud)とは?
2025 年 9 月に発覚した、npm エコシステム史上初とされる自己増殖型のワーム攻撃です。侵害された npm パッケージに仕込まれた不正コードが、インストール時(postinstall)に自動実行されて端末内の npm トークンや GitHub トークンなどの認証情報を盗み出し、その盗んだトークンを使って被害者が管理する別のパッケージにも不正コードを埋め込んで再公開する——という形で連鎖的に感染が広がりました(名前の由来は映画『デューン』に登場する巨大サンドワーム)。最終的に数百のパッケージ・週間 2,000 万件超のダウンロードに影響が及んだとされ、CISA も注意喚起を出す事態になりました。
つまずきの発端
今回のトラブルは、Claude Code でプラグインを 1 つ追加しようとした際、内部で以下のような連鎖が起きたことに始まります。
この連鎖の最後に出てくる GPG パスフレーズ入力画面(pinentry)が、最初のつまずきの元凶になります。
余談:Claude Code Marketplace / Plugin の仕組み
余談:Claude Code Marketplace / Plugin の仕組み
Claude Code Marketplace / Plugin の実体はほぼ「Git リポジトリ」です。/plugin marketplace add で Git リポジトリを登録し、/plugin install で Slash commands、Subagents、Hooks、MCP servers などをまとめた「指示書のパッケージ」を取得します。中央集権的なストアサーバーはなく、Git ホスティングをそのまま使っている形です。第三者のコードを実行することになるので、発行元の確認はしておきましょう。
余談:ハンズオン用ディレクトリを別に切るべきか?
余談:ハンズオン用ディレクトリを別に切るべきか?
ハンズオンのたびに、作業ディレクトリをどうするかで悩みます。
- 作業用に新しいディレクトリを切った方が良いのか?
- 既存プロジェクトの中で作業したら汚染されるのか?
- 別ディレクトリで Skills を使いたくなったらセットアップし直しになるのか?
Claude Code のプラグイン/マーケットプレイス/Skills は ユーザーグローバルに管理されます。ここで言う「ユーザーグローバル」とは、作業ディレクトリ単位ではなく、OS のユーザー単位で共通という意味です。
同じユーザーであれば ~/project-a/ で claude を起動しても ~/project-b/ で起動しても、同じプラグイン/Skills が使えます。具体的なユーザーグローバルの保存場所は以下です。
~/.claude/plugins/ # インストール済みプラグイン
~/.claude/plugins/marketplaces/ # 登録済みマーケットプレイス
つまり、一度 /plugin marketplace add と /plugin install を済ませれば、どのディレクトリで claude を起動しても同じプラグイン/Skills が使えます。ハンズオン後に別プロジェクトへ移動しても、セットアップし直しにはなりません。
一方で、ディレクトリ単位で分かれるものもあります。
| 分類 | 保存場所 | 共有される? |
|---|---|---|
| プラグイン/マーケットプレイス/Skills | ~/.claude/ |
✅ 全ディレクトリで共有 |
プロジェクト固有の設定(CLAUDE.md など) |
作業ディレクトリ直下 | ❌ ディレクトリごと |
| 会話履歴・セッション | 作業ディレクトリ単位 | ❌ ディレクトリごと |
このため、ハンズオンでの推奨は次のとおりです。
- プラグイン導入は既存の作業ディレクトリで OK(グローバルなので副作用が少ない)
- ハンズオンで生成するファイル(議事録、提案書、スライドなど)は専用ディレクトリを切ると後片付けが楽
- 会話履歴を分けたい/既存プロジェクトの
CLAUDE.mdに影響されたくない場合は、専用ディレクトリでclaudeを起動する
私は今回、~/work/skills-test-20260702 という専用ディレクトリを作って、そこで claude を起動しました。プラグイン自体はグローバルなので、別プロジェクトで /proposal-creation-toolkit:create-proposal を叩いても動きます。
補足:~/.claude/ の中身を覗いてみる
補足:`~/.claude/` の中身を覗いてみる
「本当にプラグインはユーザーグローバルに入っているのか?」を確認するため、実際に ~/.claude/ の中身を見てみます。
$ ls -la ~/.claude/
drwxr-xr-x 2 emiki emiki 4096 Jul 6 16:01 backups
drwxr-xr-x 2 emiki emiki 4096 Jan 9 09:56 cache
drwx------ 2 emiki emiki 4096 Jul 2 12:00 debug
drwxr-xr-x 8 emiki emiki 4096 Jul 6 14:36 file-history
-rw-r--r-- 1 emiki emiki 163698 Jul 6 16:01 history.jsonl
drwxr-xr-x 2 emiki emiki 4096 Jul 6 10:56 ide
drwxr-xr-x 2 emiki emiki 4096 Jul 3 11:53 paste-cache
drwxr-xr-x 2 emiki emiki 4096 Jul 6 14:36 plans
drwxr-xr-x 5 emiki emiki 4096 Jul 6 13:43 plugins # ★ プラグイン本体
drwxr-xr-x 6 emiki emiki 4096 Jul 6 08:49 projects # ★ プロジェクトごとの記録
drwxr-xr-x 11 emiki emiki 4096 Jul 6 14:37 session-env
drwx------ 2 emiki emiki 4096 Jul 6 16:01 sessions # ★ 会話履歴
-rw------- 1 emiki emiki 399 Jul 3 14:45 settings.json
(略)
一見ゴミが多そうに見えますが、それぞれ役割があります。今回の話で重要なのは以下の 3 つです。
| ディレクトリ/ファイル | 役割 |
|---|---|
plugins/ |
インストール済みのプラグイン本体。全プロジェクト共通で使われる |
projects/ |
プロジェクト(作業ディレクトリ)ごとの記録(CLAUDE.md のキャッシュなど) |
sessions/ |
会話履歴。プロジェクトごとに分かれている |
さらに plugins/ の中を見てみます。
$ ls ~/.claude/plugins/
cache config.json installed_plugins.json known_marketplaces.json marketplaces plugin-catalog-cache.json repos
$ ls ~/.claude/plugins/marketplaces/
claude-plugins-official consulting-team-plugins
marketplaces/ の下に、登録した 2 つのマーケットプレイス(Anthropic 公式と、社内組織のもの)がディレクトリとして展開されています。冒頭で説明したとおり、マーケットプレイスの実体は Git リポジトリのクローンであることが、ここでも確認できます。
なおセキュリティ観点で見ると、~/.claude/plugins/marketplaces/<マーケットプレイス名>/ には Slash commands や hooks のスクリプトがそのまま置かれています。マーケットプレイスは第三者コードを自分の環境に展開する仕組みなので、追加前に発行元の信頼性を確認しておきましょう。
奮闘記録タイムライン
今回のハンズオンの内容は冒頭のリンクの通りです。
つまずきの大半は事前準備段階で発生しました。事前準備の内容はざっくり以下のとおりです。
- マーケットプレイス登録 + プラグイン導入
- Claude Code で
/pluginを実行 - "Marketplace → Add marketplace" を選択
- 事前に共有したマーケットプレイス URL を入力
- プラグイン一覧から "提案書作成ツールキット" を選択してインストール
- Claude Code で
- (任意) marp-cli の準備:Marp スライド生成まで体験する場合は marp-cli も実行できるように。
ここから、実際にぶつかった 6 つの「つまずき」を時系列で紹介します。
つまずき 1:ターミナルが謎の文字列で埋まる「画面崩壊バグ」
症状
Claude Code で Skills のプラグインをインストールするために、
- Claude Code で
/pluginを実行 - "Marketplace → Add marketplace" を選択

- 事前に共有したマーケットプレイス URL を入力
と準備を進めたところ、突然画面が以下のような意味不明な文字列で埋まり、TUI(ターミナル UI)が崩壊しました。
5;37;32M5;40;27M5;42;24M5;43;23M5;44;22M5;46;21M5;47;21M ...

💡 CUI ではなく TUI と呼ぶ理由
CUI が「コマンドを打つと結果が下に流れていく」形式なのに対し、TUI(Text-based User Interface)は、ターミナルに「色を変えろ」「カーソルを動かせ」と命令する特殊な文字列(ANSI エスケープシーケンス)を使ってターミナル画面全体を書き換え、アプリのように描画する方式です。Claude Code は後者。今回のバグはこの画面描画が pinentry(GPG のパスフレーズ入力画面を出すツール)と衝突して壊れた話なので、CUI ではなく TUI と呼んでいます。
1 回目は「たまたま」と思いましたが、同じ現象が 3 回繰り返しました。
原因
この文字列は、本来なら ANSI エスケープシーケンス(ESC[5;37;32m のような形)として解釈されるはずが、先頭の ESC 文字(\x1b)だけが欠落し、残りの数字部分がそのまま生文字として画面に出力されてしまった状態でした。
原因を辿ると、以下の流れが判明しました。
/plugin実行時、内部で GitHub へのアクセス(git clone)が発生する- 前提で説明した認証構成(GCM + GPG + pass)により、保存済みトークンを復号するため GPG パスフレーズが必要になる
pinentry-curses(GPG がパスフレーズ入力用に起動する、ターミナル向けのプロンプトツール)が起動する- Claude Code のフルスクリーン描画と、pinentry が同じ端末を同時に奪い合って衝突する
- その結果、ANSI エスケープシーケンスが正しく解釈されず、崩壊した表示になる
試したこと・解決
まずは Ctrl+C で抜けます。
その後、対策として「Claude Code を起動する前に、あらかじめ GPG パスフレーズを一度入力してキャッシュさせておく」という運用にしました。GPG エージェントは、実際に GPG を使ったタイミングでパスフレーズをメモリにキャッシュしてくれます。逆に言うと、GPG を一度も使わなければキャッシュは空のままなので、先に何か GPG に仕事をさせてキャッシュを温めておく必要があります。
そのために以下コマンドを打っておきます。
# 適当な文字列に GPG で署名させ、パスフレーズ入力を先に済ませておく
echo "test" | gpg --clearsign > /dev/null
test という文字列に GPG で署名させることでパスフレーズ入力を発生させています。署名結果自体には用がないので /dev/null に捨てています。
すると、以下のように画面が崩壊せず、GPG キーのパスフレーズを入力する画面が無事表示されました。(1 回入力時失敗していますが)

この GPG キーのパスフレーズがキャッシュされた状態で claude コマンドで Claude Code を起動すれば、ターミナルでの描画の奪い合いが発生せず、git clone に進めるというわけです。
さらに、パスフレーズの再入力頻度を減らすため、GPG エージェントのキャッシュ時間を延長しました。
# ~/.gnupg/gpg-agent.conf に追記
echo "default-cache-ttl 28800" >> ~/.gnupg/gpg-agent.conf
echo "max-cache-ttl 28800" >> ~/.gnupg/gpg-agent.conf
gpg-connect-agent reloadagent /bye
28800 秒 = 8 時間で、業務時間中 1 回入力すれば持続します(デフォルトだと 10 分/最大 2 時間で、頻繁に入力を求められてしまいます)。
今回の対処
WSL + Claude Code + GPG 認証構成の環境では、Claude Code を起動する前に一度 GPG 署名コマンドを打ってパスフレーズをキャッシュしておくと、TUI と pinentry の衝突を避けられます。あわせてキャッシュ時間も延ばしておくと快適です。
根本解決というよりは「起動前におまじないを打つ」運用でしのぐ形なので、もっとスマートな回避策をご存知の方がいたら教えてください。
つまずき 2:マーケットプレイス追加で 403 エラー
症状
画面崩壊の問題を乗り越え、いよいよ /plugin からハンズオンで指定されたマーケットプレイス(社内組織の GitHub リポジトリ)を追加しようとしたところ、以下のエラーで弾かれました。
Failed to clone marketplace repository: HTTPS authentication failed. Please
ensure your credential helper is configured (e.g., gh auth login).
Original error: Cloning into '/home/emiki/.claude/plugins/marketplaces/temp_...'...
remote: Write access to repository not granted.
fatal: unable to access 'https://github.com/.../....git/':
The requested URL returned error: 403

ブラウザからは普通にリポジトリが見える(read 権限はある)のに、CLI からは 403 で拒否される状態でした。
原因
既存の GitHub Fine-grained Personal Access Token (PAT) は、Resource owner が個人アカウント(emi-ki)のもの 1 本しか発行しておらず、ハンズオンで指定されたリポジトリを保有する組織(Organization)を Resource owner とする PAT を作っていなかったためです。

Fine-grained PAT は「誰(= Resource owner)が保有するリソースに対するトークンなのか」を発行時に 1 つだけ選ぶ仕組みになっており、個人アカウントを Resource owner としたトークンでは、組織所有のリポジトリには(たとえ自分がその組織のメンバーであっても)アクセスできません。今回で言えば、Resource owner のドロップダウンに emi-ki(個人)/xxx-org(組織)/yyy-org(組織)といった候補が並んでいるうち、emi-ki を選んだ PAT しか持っていなかった、という状態です。
ブラウザから対象リポジトリが見えていたのは、ブラウザ側の GitHub ログインセッション(組織 SSO 認証済み)で表示できていただけで、CLI から使われる PAT 認証とは別物です。CLI 側は「組織を Resource owner とする PAT」を持っていなかったため、書き込み系の操作を伴う判定で 403(Write access to repository not granted)として弾かれていました。
検討した選択肢
| 選択肢 | 内容 | 採用したか |
|---|---|---|
| A | gh auth login に乗り換えて SSO 込みで再認証 |
検討したが不採用 |
| B | 既存 PAT を編集して Organization 権限を追加 | 一部検討 |
| C | SSH で clone(PAT 認証を回避) | 検討したが不採用 |
| D | 個人用と組織用で PAT を 2 本立てにする | ✅ 採用 |
エラーメッセージ自体が「gh auth login してください」と誘導していたため最初は A 案が有力に見えましたが、既存の認証構成(GCM + GPG + pass)を大きく変える手間とリスクを避けるため、個人用トークンはそのままに、組織用トークンをもう 1 本追加発行する方針にしました。最小権限の原則にも沿った形になります。
解決手順
- https://github.com/settings/personal-access-tokens/new を開く
- Token name を分かりやすく設定(例:
xxxx-organization-20260702) - Resource owner で対象の組織を選択(企業の Organization は SSO の承認が必要な場合があるので要注意)
- Repository access は
Only select repositoriesにして、必要なリポジトリだけを選択

- Repository permissions は今回は
Contents: Read and write


Generate tokenでトークン文字列(github_pat_...)を発行し、必ず控える(1 回しか表示されません)。

私は 1Password に入れておきました。

ここで権限を絞るのは、後述するつまずき 4 の「Shai-Hulud」のような、npm パッケージ経由で端末上の GitHub トークンが盗まれるタイプのサプライチェーン攻撃を踏まえた対策です。万一トークンが漏洩しても被害範囲を局所化しておくという考え方で、npm/npx の利用制限も Fine-grained PAT のスコープ限定も根っこは同じ Shai-Hulud 対策です。
トークンを GCM に登録します。まず、リポジトリごとに認証情報を分けて保存できるようにするため、事前に以下の設定を追加しておきます。
git config --global credential.https://github.com.useHttpPath true
デフォルトでは GCM は github.com に対して 1 つの認証情報しか持てませんが、useHttpPath = true を有効にすると URL のパス部分(<組織>/<リポジトリ> の階層)ごとに認証情報を保存できるようになります。これで、個人用トークンと組織用トークンを 1 台の PC 上で共存させられます。
設定できたら .gitconfig を確認します。
cat ~/.gitconfig
以下のように [credential "https://github.com"] のブロックが増えていれば OK です。
[user]
name = emi-ki
email = xxxxxxxx+emi-ki@users.noreply.github.com
[credential]
helper =
helper = /usr/local/bin/git-credential-manager
credentialStore = gpg
[credential "https://dev.azure.com"]
useHttpPath = true
[credential "https://github.com"] # ★ 追加された
useHttpPath = true
次に、新しく発行した組織用 PAT を GCM に登録します。今回は GCM が用意している専用のログインコマンド git credential-manager github login を使いました。
git credential-manager github login
実行すると「Connect to GitHub」という GUI ダイアログが別ウィンドウで立ち上がります(WSLg 経由で Windows 側に表示されます)。

- 上部のタブで
Tokenを選択 - 入力欄に新しく作った組織用 PAT を貼り付け
Sign inをクリック
これで GCM が PAT を受け取り、GPG で暗号化して pass(~/.password-store/)に保存してくれます。
💡 WSLg(WSL に標準搭載されている GUI サポート機能)のおかげで、WSL 内の Linux アプリでも Windows のウィンドウとして GUI を表示できます。
💡 GUI ダイアログが出るのは、GCM がデスクトップ環境(WSLg 含む)を検出しているためです。SSH 接続などで GUI が使えない環境の場合は、ターミナル内の対話プロンプトにフォールバックします。
これで PAT の登録が完了しました。
最後に、リソースオーナーが私個人(emi-ki)のリポジトリと、リソースオーナーが組織のリポジトリの両方に実際にアクセスできるか git ls-remote で確認します。
# GPG パスフレーズのキャッシュが切れていたら先に温めておく
echo "test" | gpg --clearsign > /dev/null
# 個人リポジトリ
LC_ALL=C git ls-remote https://github.com/<個人アカウント>/<リポジトリ>.git | head -3
▼実行結果例 ※Amazon Q CLI を使い会社診断アプリを Vibe Coding で作成してみた | DevelopersIO で使用したリポジトリをサンプルとして指定しています
emiki@<hostname>:~/work/skills-test-20260702$ LC_ALL=C git ls-remote https://github.com/emi-ki/company-match-quiz.git 2>&1 | head -3
933d531a274338d79e1fc88fc79b6f70506ab4ad HEAD
933d531a274338d79e1fc88fc79b6f70506ab4ad refs/heads/main
emiki@<hostname>:~/work/skills-test-20260702$
# 組織リポジトリ
LC_ALL=C git ls-remote https://github.com/<組織>/<リポジトリ>.git | head -3
▼実行結果例
emiki@<hostname>:~/work/skills-test-20260702$ LC_ALL=C git ls-remote https://github.com/<組織>/<リポジトリ>.git 2>&1 | head -3
16ff9cf20e793a7e766db4dbad81bbcd62d486b2 HEAD
fc62419e3693052bcf86067ed167f58841171c7b refs/heads/feat/add-plugins-xxxxxxxx
301fd73c505b6e93c3db2c6e3f4f30a8c9be129c refs/heads/feature/xxxxxxxx
emiki@<hostname>:~/work/skills-test-20260702$
どちらも HEAD やブランチ名の一覧がずらっと返ってくれば、個人用トークンと組織用トークンがリポジトリごとに正しく使い分けられている証拠です。ここまで来れば、Claude Code の /plugin marketplace add は認証プロンプトを出さずにそのまま通ります。
教訓
組織リポジトリで 403 エラーが出たら、まず自分の Fine-grained PAT の Resource owner を確認する。個人用と組織用でトークンを分けておくと、後々のトラブルシュートもしやすくなります。
余談:新トークンは本当に別物が使われているのか?
余談:新トークンは本当に別物が使われているのか?
つまずき 2 を解決した直後、2 本の PAT がちゃんと別物として共存できているのか不安になりました。git credential-manager github list の結果は emi-ki の 1 行しか返ってこず、これだけでは個人用と組織用の 2 つの PAT が保存されているのか判別が付かなかったからです。
そこで、pass の保存先である ~/.password-store/ の中身を直接覗いてみました。pass は「1 パスワード = 1 ファイル(.gpg)」で保存する仕組みなので、.gpg ファイルの一覧を見れば、保存されている PAT の本数と対象がそのまま分かります。
find ~/.password-store/ -name "*.gpg" 2>/dev/null
▼実行結果例
emiki@<hostname>:~/work/skills-test-20260702$ find ~/.password-store/ -name "*.gpg" 2>/dev/null
/home/emiki/.password-store/sts.GetSessionToken,...gpg ← 別用途(AWS STS の一時トークン)
/home/emiki/.password-store/default.gpg ← pass 初期化時のダミー
/home/emiki/.password-store/git/https/github.com/<個人アカウント>.gpg ← ① 個人用 PAT
/home/emiki/.password-store/git/https/github.com/<組織>/<リポジトリ>.git/<個人アカウント>.gpg ← ② 組織用 PAT
emiki@<hostname>:~/work/skills-test-20260702$
上記のとおり、GitHub 用の PAT は ① と ② の 2 ファイルに分かれて保存されていることが確認できました。ファイル名末尾(<個人アカウント>)は同じですが、その手前のディレクトリ階層が違うため別ファイルです。GCM はアクセス先の URL に応じて、正しい方のファイルを選んで復号してくれます。
つまり git credential-manager github list で 1 行しか出なかったのは、「認証上のユーザー名」欄はトークン発行者本人(自分自身)のままになる仕様だったからで、実体としては pass の中でパス階層によって 2 本が正しく共存できていました。
最終的には両方のリポジトリに git ls-remote が通ることを確認して安心しました。
git ls-remote https://github.com/<個人アカウント>/<リポジトリ>.git
git ls-remote https://github.com/<組織>/<リポジトリ>.git
# どちらも成功すれば、2 本の PAT が正しく共存している
つまずき 3:GPG パスフレーズキャッシュ切れの再発
症状
1 日では Skills のハンズオンが完了しなかったため、翌日 /plugin を実行するところから再開すると、再び認証エラーに遭遇しました。
fatal: Failed to decrypt file ...
gpg: public key decryption failed: No passphrase given
原因
GPG エージェントのキャッシュが切れていたためです。
解決
つまずき 1 と同じおまじないで復旧します。
echo "test" | gpg --clearsign > /dev/null
今回の対処
default-cache-ttl を延ばしておいても、WSL や gpg-agent プロセス自体が再起動すればキャッシュは消えます。「Claude Code を使う日は作業開始時に一度おまじないコマンドを打つ」という運用でしのごうと思います。
もっとスマートな回避策をご存知の方がいたら教えてください。
余談:Marketplace のタブ構成と bash モードのクセ
余談:Marketplace のタブ構成と bash モードのクセ
つまずき 3 のあとにマーケットプレイス追加を再試行した際、「Cloning...」の表示が消えて成功か失敗か分からず戸惑いました。実際にはクローンは成功しており、「Discover タブ=インストール可能な一覧」「Installed タブ=インストール済み一覧」の区別が付いていなかっただけでした。Marketplaces / Errors タブで状態を確認し、Discover タブから検索してインストールする、という流れで対応すれば良さそうです。


また、Claude Code の bash モードでは !ls ~/.claude は通るのに、末尾スラッシュ付きの !ls ~/.claude/ は認識されない、というクセもありました。
つまずき 4:Shai-Hulud 対策の古いエイリアスが残っていた
症状
ハンズオンの手順で npx @marp-team/marp-cli を使う場面があったのですが、npm --version を実行すると以下のような警告が出て止まってしまいました。
emiki@HL01290:~/work/skills-test-20260702$ npm --version
WARNING: npm は実行しないでください
emiki@HL01290:~/work/skills-test-20260702$
原因
これは Shai-Hulud を受けた社内方針の名残でした。当時は社内方針で npm/npx の実行が原則禁止となり、.bashrc に実行を塞ぐエイリアスを設定していました。その後、npm の min-release-age 機能(公開から一定期間経過したバージョンのみインストールを許可する設定)を有効にすることを条件に利用が緩和されたのですが、エイリアスの設定が残っていたため、npm/npx が自分の古い設定で動かない、という状態になっていたのです。
試したこと・解決
- バックスラッシュでエイリアスを回避しつつ、現在のバージョンを確認
\npm --version
- npm を最新化
\npm install -g npm@latest
min-release-ageを設定(以前 pnpm 用に社内向けに調べていた値を転用)
npm config set min-release-age 30240
.bashrcをバックアップしてから、npm/npx を塞いでいたエイリアスの行を削除
cp ~/.bashrc ~/.bashrc.backup
# エディタ(vim、nano など)で該当行を手動削除
- ここでハマりました。
source ~/.bashrcしてもtype npmが「aliased to...」のままで、npm --versionは警告を出し続けました。原因は「ファイルから削除しても、現行シェルのメモリ上に残っているエイリアスはsourceでは消えない」という bash の仕様です。
unalias npm
unalias npx
これでようやく npm コマンドが通るようになりました。
教訓
.bashrc を編集して source しても、今動いているシェルに残ったエイリアスはそれだけでは消えません。直らない場合は unalias するか、新しいターミナルを開き直しましょう。また、社内方針に従って入れたセキュリティガードは、方針が緩和されても手元の設定まで見直すのを忘れがちなので注意。
⚠️ 順序の反省点
実際は「npm 最新化 → min-release-age 設定」の順で叩いてしまいましたが、Shai-Hulud 対策の観点では min-release-age を設定してから npm install を叩く方が良かったです。npm install はネットワーク経由でパッケージを取得し postinstall などのフックスクリプトが自動実行されるため、ガードレールなしで実行するとこの瞬間だけリスクにさらされます。
ただし今回のように min-release-age 機能自体が npm v11.10.0 以上でしか動作しない場合、そもそも古い npm では設定してもガードが効かず、先に更新するしかないという構造的なジレンマがあります。より安全に倒すなら、以下のような選択肢があります。
-
--ignore-scriptsを付けてpostinstallを封じた状態で更新する:\npm install -g --ignore-scripts npm@11.10.0- Shai-Hulud 系の攻撃は、パッケージ取得時に自動実行される
postinstallスクリプトを発火点にしていることが多いため、そこを封じるだけでもリスクを下げられます
- Shai-Hulud 系の攻撃は、パッケージ取得時に自動実行される
-
npm 経由ではなく Node.js を再インストールする:OS のパッケージマネージャや
nvmなどで最新の Node.js(=新しい npm 同梱)を入れる- この方法だと npm レジストリを一度も経由せずに新しい npm が手に入るため、「npm レジストリ側の侵害」というリスク経路を回避できます
つまずき 5:新しいリポジトリで 403 エラー
症状
ハンズオン用の別リポジトリを clone しようとしたところ、再び PAT 入力を求められた末に失敗しました。
原因
つまずき 2 で発行した組織用 PAT は「1 リポジトリのみ許可」の設定で作っていたため、新しいリポジトリはそもそもアクセス対象に含まれていませんでした。
解決
ここでも新規 PAT を発行するのではなく、既存の組織用 PAT の「Repository access」を編集して、対象リポジトリを追加しました。
- https://github.com/settings/tokens?type=beta を開く
- 対象のトークンを選択し、Repository access を Edit
- 新しいリポジトリを追加して Save
- GPG パスフレーズを再キャッシュ(
echo "test" | gpg --clearsign > /dev/null) git ls-remoteで疎通確認git cloneで成功を確認
git ls-remote https://github.com/<組織>/<新リポジトリ>.git
git clone https://github.com/<組織>/<新リポジトリ>.git
つまずき 2 とつまずき 5 では「今ある構成を大きく変えず、最小限の追加設定で押し切る」という同じ方針で対処しました。gh CLI は結局一度もインストールせず、GCM + GPG + pass 構成のまま完走しています。
教訓
Fine-grained PAT を「1 リポジトリのみ許可」で作った場合、新しいリポジトリが増えるたびに Repository access を編集する必要があります。頻繁に新しいリポジトリを触るなら、最初から対象範囲を広めに設計するか、リポジトリ追加の手順をルーティン化しておくと楽になります。
つまずき 6: npm が使えるよう設定したが、pnpm が使われる
症状
Marp スキルでスライドのプレビューを生成する際、内部で以下のコマンドが実行されようとしました。
pnpm dlx @marp-team/marp-cli
せっかく npm を使えるようにしたのに pnpm が出てきて一瞬戸惑いました。
原因
スキル本体(~/.claude/plugins/marketplaces/<リポジトリ名>/plugins/xxx-toolkit/commands/marp-xxx.md)の中身を確認したところ、pnpm/npx の実行に関する記述は一切ありませんでした。スキルのミッションは「作成したマークダウンを Marp 形式のスライドマークダウンに変換すること」までで、生成したマークダウンを marp-cli で HTML/PDF に変換する処理はスキルの守備範囲外です。
つまり、pnpm dlx @marp-team/marp-cli を選んだのはスキルではなく Claude Code 自身でした。プレビュー確認のために自主的にコマンドを組み立てた際に、環境に pnpm が入っていたのを確認したのか、Marp 公式ドキュメントの慣習を学習していたのか、色々考えて pnpm dlx を採用されたと考えられます。
今回の対処
pnpm が動くことは確認済みだったので、無理に書き換えずそのまま採用しました。結果、43 スライドの HTML プレビューが「Renders cleanly with no errors」で生成されました。
結果
画面が謎の文字列で埋まって何もできなかった状態から、議事録ファイル 1 枚渡すだけで提案書とスライドが自動生成できる状態までなんとか到達しました。やった~

おわりに
なんとか Skills を Windows 端末の WSL 上で使えるところまで漕ぎ着けました。
Claude Code そのものより、事前に自分で組んでいた Git 認証構成や社内方針の名残との折り合いに手こずったつまずきが大半でした。私自身は開発者ではないので、開発者の方から見れば凡ミスに見える箇所もあったかもしれません。それでも Windows にこだわって記録を残しているのは、Windows を利用されているお客様がたくさんいらっしゃるからです。同じ環境で Claude Code Skills を触ろうとしている方の一助になれば幸いです。
ちなみに、最後 Marp の体裁を整えるのが面倒だなと思っていたら、戸田さんに「自分で Marp を書こうとしない方がいいですよ、画像フォルダに画像を置いて、Claude に『ここに配置して』と頼めばやってくれますよ」と言われ、「たしかに」となりました。なぜ Marp を自分で書こうとしてしまったのか。それこそ AI に頼めばいいんですね。
本記事への質問やご要望については画面下部の「DevelopersIOへのご意見」からお問い合わせいただけます。
参考





