Skip to content

🐛 Windows で plugin install が ENOTDIR になる symlink 依存を外す - #8

Open
rinjugatla wants to merge 1 commit into
u-ichi:mainfrom
rinjugatla:fix/windows-plugin-install-symlink
Open

rinjugatla wants to merge 1 commit into
u-ichi:mainfrom
rinjugatla:fix/windows-plugin-install-symlink

Conversation

@rinjugatla

@rinjugatla rinjugatla commented Aug 8, 2026 •

Copy link
Copy Markdown

概要

Windows ネイティブ環境で claude plugin install が失敗し、この plugin を導入できない問題を修正します。

README の手順どおり marketplace の登録までは成功しますが、続く install が ENOTDIR で落ちます。

$ claude plugin marketplace add u-ichi/reviewable-html-workbench
✔ Successfully added marketplace: reviewable-html-workbench-local

$ claude plugin install reviewable-html-workbench
Installing plugin "reviewable-html-workbench"...
✘ Failed to install plugin "reviewable-html-workbench": ENOTDIR: not a directory, scandir 'C:\Users\<user>\.claude\plugins\marketplaces\reviewable-html-workbench-local\plugins\reviewable-html-workbench'

原因は、marketplace の source が指す plugins/reviewable-html-workbench が repo root (..) への symlink であることです。Windows の git は既定で core.symlinks=false(Git for Windows のインストーラが system gitconfig へ書き込む)のため、symlink を「リンク先文字列 .. が入った 2 バイトの通常ファイル」として checkout します。Claude Code はそこを plugin root として scandir するので、走査した時点で ENOTDIR になります。

symlink の解決先は元々 repo root なので、source を repo root 自身へ向ければ、経路が 1 段短くなるだけで解決先は変わりません。POSIX 上でこの等価性が実際に成り立つことはテスト計画で実測しています。

変更内容

  • .claude-plugin/marketplace.json … source を ./plugins/reviewable-html-workbench → ./ に変更。
  • .agents/plugins/marketplace.json(Codex)… source.path も同様に ./ に変更。Codex 側も同じ symlink を参照しており、Windows では同じ失敗をするため。
  • plugins/reviewable-html-workbench symlink を削除(参照元が無くなるため plugins/ ディレクトリごと消滅)。
  • tests/test_project_layout.py
    • test_codex_marketplace_entry_points_to_plugin_root … symlink 前提のアサーションを新実装の期待値へ更新。
    • test_claude_marketplace_entry_points_to_plugin_root … Claude 側 source の検査を新規追加(従来は version しか検証しておらず、source の変更を検出できなかったため)。
    • test_repo_contains_no_symlinks … git ls-files -s で mode 120000 の tracked file が無いことを検査する回帰テストを新規追加。同じ踏み方の再発を防ぐため。
  • docs/codex-plugin-packaging.md / docs/design.html / docs/development-plan.html … symlink 構成の記述を更新し、廃止した理由を記録。

なお AGENTS.md には「commit 時に 4 ファイルの version を必ず更新する」という規約がありますが、本 PR では意図的に version を bump していません。理由は「補足」に記載します。

テスト計画

Windows での install(Windows 11 / Git for Windows 2.54.0 / core.symlinks=false)

symlink が通常ファイルへ壊れたままの checkout を marketplace として登録し、install が通ることを確認しました。

✔ Successfully added marketplace
✔ Successfully installed plugin: reviewable-html-workbench (scope: user)

~/.claude/plugins/cache/.../skills/ に plan-preview / reviewable-design-doc / visual-html-renderer の 3 skill が実体として展開されることも確認しています。

Linux での等価性確認(修正前 cache と修正後 cache の比較)

「Windows でだけ通って他の OS を壊していないか」を確かめるため、Linux コンテナ(node:22-slim + claude 2.1.226)で 修正前の構成(symlink を復元し source を旧パスへ戻したもの) と 修正後の構成 を別 marketplace として両方 install し、生成された cache を比較しました。

$ diff -r --exclude=plugins --exclude=marketplace.json <修正前のcache> <修正後のcache>
(差分なし)

差が出たのは、廃止した symlink 自身と marketplace.json の該当 1 行だけです。Claude Code が plugin root として受け取る内容は修正前後で完全に一致するため、POSIX 側の挙動は変わりません。修正後の構成のみでの install 成功(skill 3 件の展開を含む)も同じ環境で確認済みです。

自動テスト(Windows 11 / Python 3.11.14)

$ python -m unittest discover -s tests
Ran 316 tests in 34.3s
FAILED (failures=1, errors=10)

残る 11 件は、.sh を直接 exec できない Windows 環境固有の既存の失敗です(test_plugin_dev_switch / test_kill_review_preview_helper / test_export_pdf、いずれも WinError 193)。本 PR とは無関係で、Linux 環境では発生しません。

本 PR 変更前は同じ環境で 12 件失敗しており、symlink 起因の test_codex_marketplace_entry_points_to_plugin_root が 1 件解消しています(Ran 314 tests / FAILED (failures=2, errors=10))。

新規追加した test_repo_contains_no_symlinks は、symlink を復元した使い捨て copy で落ちることを確認しています。

plugin validation

$ claude plugin validate .
✔ Validation passed

4 つの manifest(.claude-plugin/plugin.json / .claude-plugin/marketplace.json / .codex-plugin/plugin.json / .agents/plugins/marketplace.json)の JSON 構文も確認済みです。

Checklist

  • Tests pass
  • Plugin validation passes

補足

version を bump していないことについて

AGENTS.md の規約では commit ごとに 4 ファイルの version を更新することになっていますが、本 PR ではあえて据え置き(1.28.2 のまま)にしています。

README のローカル導入手順(claude plugins install .)が現行 CLI では通らない問題の修正 PR を並行して出す予定で、両方が version を上げると衝突するためです。bump の要否と値(本 PR は挙動を変えない packaging 修正なので patch 相当と考えていますが、もう一方とまとめて 1 回にする判断もあり得ます)は作者にお任せします。ご指示いただければこの PR 側で bump します。

test_all_version_files_are_in_sync は 4 ファイルの一致を検査するテストで、据え置きでも一致しているため pass します。

core.symlinks=true による回避について

core.symlinks=true にすれば修正前の構成でも install は成立します(実測で確認しました)。ただし恒久的な解決にはなりません。

  • symlink の作成権限(管理者権限または Developer Mode)に依存します。
  • Claude Code は内部で git clone を実行するため -c を渡せず、marketplace add の前に global / system の gitconfig へ書いておく必要があります。
  • global 由来の core.symlinks=true は clone 先の .git/config に記録されません。後で false に戻すと次の checkout で通常ファイルへ戻り、再発します。checkout 自体はエラーを出さないため、失敗が表面化するのは install の時です。
  • 既に core.symlinks=false で clone 済みの marketplace は local config が優先されるため、global を変えても直らず marketplace remove → add のやり直しが必要です。

symlink 自体を廃止すれば、これらの前提がすべて不要になります。

Codex 側の実機検証について

.agents/plugins/marketplace.json の変更は、手元に Codex CLI が無いため実機での install 検証ができていません。変更内容は Claude 側と同じ性質(source.path を symlink 経由から repo root 直指定へ)で解決先も同一ですが、Codex 環境をお持ちの方に一度ご確認いただけると確実です。


🤖 このPRは Claude Code(Claude Opus 5)を使用して作成しました。

Windows で marketplace を登録した後の `claude plugin install` が
`ENOTDIR: not a directory, scandir '...\plugins\reviewable-html-workbench'`
で失敗していた。

原因は marketplace の source が指す plugins/reviewable-html-workbench が
repo root (..) への symlink だったこと。Windows の git は既定で
core.symlinks=false (Git for Windows のインストーラが system gitconfig へ
書く) のため、symlink をリンク先文字列 ".." が入った 2 バイトの通常ファイル
として checkout する。そこを plugin root として走査した時点で落ちる。

symlink の解決先は元々 repo root なので、source を repo root 自身へ向ければ
経路が 1 段短くなるだけで解決先は変わらない。Claude 側
(.claude-plugin/marketplace.json の source) と Codex 側
(.agents/plugins/marketplace.json の source.path) を "./" にし、symlink は
廃止した。tracked file に symlink を持ち込まないことを test で固定する。
symlink 前提だった test_codex_marketplace_entry_points_to_plugin_root は
新実装の期待値へ更新し、Claude 側 source の検査を新規に追加。

検証: Windows では symlink が壊れたままの copy から marketplace add と
plugin install が通り、cache に skill 3 件が展開されることを確認。Linux
コンテナでは修正前の構成 (symlink を復元し source を旧パスへ戻したもの) と
修正後の構成を両方 install し、生成された cache が plugins/ と
marketplace.json の 1 行を除いて完全に一致することを diff -r で確認した。
unittest は .sh を exec できない Windows 環境固有の既存失敗 10 件を除き pass
(修正前は symlink 起因の 1 件を加えた 11 件)。Codex CLI が無いため Codex 側の
実機検証は未了。

core.symlinks=true での回避も成立はするが、symlink 作成権限に依存し、かつ
global 由来の設定は clone 先の .git/config に記録されないため、false へ
戻すと次の checkout で再発する。symlink 自体を廃止すれば依存が消える。

version は bump していない。`claude plugin install` のローカル導入手順の修正を
別 PR で並行して出す予定で、双方が version を上げると衝突するため、bump の
要否と値の判断は作者に委ねる (AGENTS.md の規約に対する意図的な逸脱)。
@rinjugatla
rinjugatla force-pushed the fix/windows-plugin-install-symlink branch from 87c99b1 to 421a123 Compare August 8, 2026 08:15
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