8eb4c58d2d
根因不是「Desktop 不跑 hook」:Desktop 1.52386.0/CC 2.1.266 內實測專案 PreToolUse hook 照擋。真正斷的是 ~/.claude/settings.json extraKnownMarketplaces.inkstone.source 多了 path,CC 把它當 git 抓取欄位, marketplace 被忽略 ⇒ isep@inkstone「Marketplace inkstone not found」, 59 支閘在 CLI 與 Desktop 一起失效,而且沒有任何閘講得出來。 本支從 plugin 外面查(claude plugin list --json + settings 對 known_marketplaces), 點名多出的欄位並印出一行修法。迴歸測試 scripts/test-isep-doctor.sh 5/5。 假設:修 ~/.claude/settings.json 是 leo 的個人設定,由 leo 親手執行,本 commit 不動它。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
4.3 KiB
Python
Executable File
103 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""isep-doctor — 這台機器的 Claude Code 到底有沒有把 ISEP 載進來(inkstone/ISEP#150)
|
||
|
||
為什麼要有這支:
|
||
plugin 載入失敗時,ISEP 的 59 支閘**一支都不會跑**,而且**沒有任何閘能講出來**——
|
||
講話的 isep-presence-beacon 本身就在那個沒載入的 plugin 裡。
|
||
2026-09-05 起 `~/.claude/settings.json` 的 `extraKnownMarketplaces.inkstone.source`
|
||
多了一個 `path`(本機 checkout 路徑),Claude Code 把它當成 git 來源的抓取欄位
|
||
(debug log:「its network source differs from the one declared for it in settings
|
||
(… path …)」),整個 marketplace 被忽略 ⇒ `isep@inkstone` 報 "Marketplace inkstone not found"。
|
||
CLI 與 Desktop 同病,當時被誤判成「Desktop 不跑 hook」。
|
||
|
||
判準(狀態,不是措辭):
|
||
① `claude plugin list --json` 裡 isep@inkstone 帶 errors ⇒ ❌
|
||
② settings 對 inkstone 的宣告,比 known_marketplaces.json 記的多了抓取欄位 ⇒ 指出是哪一個、給修法
|
||
都沒有 ⇒ ✅,離開碼 0
|
||
|
||
離開碼:0 通|1 沒載入(附原因與一行修法)|2 查不了(找不到 claude 可執行檔等)
|
||
|
||
測試用環境變數:ISEP_DOCTOR_PLUGIN_LIST_JSON(直接給 plugin list 的輸出檔)、
|
||
ISEP_DOCTOR_CLAUDE_HOME(代替 ~/.claude)。迴歸測試:scripts/test-isep-doctor.sh
|
||
"""
|
||
import glob
|
||
import json
|
||
import os
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
|
||
PLUGIN_ID = "isep@inkstone"
|
||
MARKET = "inkstone"
|
||
FETCH_FIELDS = ("path", "ref", "headers", "sparsePaths")
|
||
|
||
home = os.environ.get("ISEP_DOCTOR_CLAUDE_HOME") or os.path.expanduser("~/.claude")
|
||
|
||
|
||
def find_claude():
|
||
c = shutil.which("claude")
|
||
if c:
|
||
return c
|
||
cands = sorted(glob.glob(os.path.expanduser(
|
||
"~/Library/Application Support/Claude/claude-code/*/claude.app/Contents/MacOS/claude")))
|
||
return cands[-1] if cands else None
|
||
|
||
|
||
def plugin_list():
|
||
f = os.environ.get("ISEP_DOCTOR_PLUGIN_LIST_JSON")
|
||
if f:
|
||
return json.load(open(f))
|
||
exe = find_claude()
|
||
if not exe:
|
||
return None
|
||
out = subprocess.run([exe, "plugin", "list", "--json"], capture_output=True, text=True, timeout=60)
|
||
return json.loads(out.stdout)
|
||
|
||
|
||
def load(p):
|
||
try:
|
||
return json.load(open(p))
|
||
except Exception:
|
||
return {}
|
||
|
||
|
||
try:
|
||
plugins = plugin_list()
|
||
except Exception as e: # noqa: BLE001
|
||
print(f"◐ 查不了:claude plugin list 失敗({e})")
|
||
sys.exit(2)
|
||
if plugins is None:
|
||
print("◐ 查不了:找不到 claude 可執行檔")
|
||
sys.exit(2)
|
||
|
||
entry = next((p for p in plugins if p.get("id") == PLUGIN_ID), None)
|
||
declared = (load(os.path.join(home, "settings.json")).get("extraKnownMarketplaces") or {}).get(MARKET, {}).get("source")
|
||
known = (load(os.path.join(home, "plugins", "known_marketplaces.json")).get(MARKET) or {}).get("source")
|
||
|
||
extra = []
|
||
if isinstance(declared, dict):
|
||
extra = [k for k in FETCH_FIELDS if k in declared and (not isinstance(known, dict) or declared.get(k) != known.get(k))]
|
||
|
||
if entry is None:
|
||
print(f"❌ {PLUGIN_ID} 沒有安裝(這台的閘全部不存在)")
|
||
print(" 修法:claude plugin marketplace add https://git.uncle6.me/inkstone/ISEP.git && claude plugin install isep@inkstone")
|
||
sys.exit(1)
|
||
|
||
errors = entry.get("errors") or []
|
||
if not errors and not extra:
|
||
print(f"✅ {PLUGIN_ID} {entry.get('version')} 已載入(enabled={entry.get('enabled')})")
|
||
sys.exit(0)
|
||
|
||
print(f"❌ {PLUGIN_ID} {entry.get('version')} 沒有載入——這台 Claude Code(CLI 與 Desktop 都一樣)一支 ISEP 閘都不會跑")
|
||
for e in errors:
|
||
print(f" Claude Code 回報:{e}")
|
||
if extra:
|
||
print(f" 原因:~/.claude/settings.json 的 extraKnownMarketplaces.{MARKET}.source 多了 {', '.join(extra)},"
|
||
"Claude Code 把它當 git 抓取欄位,跟已安裝的 marketplace 對不上,整個 marketplace 被忽略")
|
||
fields = ", ".join(repr(k) for k in extra)
|
||
print(" 修法(一行,改完重開 Claude Desktop/CLI session):")
|
||
print(f" python3 -c \"import json,os;p=os.path.expanduser('~/.claude/settings.json');d=json.load(open(p));"
|
||
f"s=d['extraKnownMarketplaces']['{MARKET}']['source'];[s.pop(k,None) for k in ({fields},)];"
|
||
"json.dump(d,open(p,'w'),ensure_ascii=False,indent=2)\"")
|
||
sys.exit(1)
|