工程實作 21 min read ★ Featured

Claude Code Teams Plugin 為什麼不會自動更新?根因 + 3 步修法

Claude Code Teams Plugin 永遠不更新的根因:version string 兩端相等。3 步修法:git SHA 比對 + GitHub Action 自動 bump + Managed Settings 推 SessionStart hook,session 啟動自動拉最新。

Claude Code Teams Plugin 為什麼不會自動更新?根因 + 3 步修法
本文目錄 · 9
TL;DR
Claude Code Teams 的 plugin 自動更新預設用 version string 比對——兩端都是同一個版號,永遠不觸發。根本解法是改比 git SHA:任何 commit 都是更新訊號。搭配 GitHub Action 自動 bump 版本Managed Settings 推 SessionStart hook,團隊成員每次開 new session 就自動拿最新版,零手動操作。

📌 目錄

---

🔍 為什麼 plugin 永遠不更新:根因分析

我自己一開始也以為 Claude Code Teams 的 plugin 只要 push 新 code 就會自動同步。結果用了一段時間,發現版本號一直停在安裝當天那個。

根因很直接:預設的更新邏輯比對的是 version string

已安裝版本:0.9.11
marketplace 版本:0.9.11
→ 相等,不更新

問題在於:如果你每次 push code 都不手動改 marketplace.json 裡的版本號,兩端永遠相等,更新永遠不觸發。這是一個「邏輯正確但實際上永遠不動」的盲點。

比 version string 更可靠的訊號是 git SHA:任何 commit 都會產生新的 SHA,只要 SHA 不同就代表「有新東西」,就更新。

整個修法涉及三個層面:

層面什麼要改效果
更新腳本比 git SHA 取代 version string任何 commit 都觸發更新
GitHub Action每次 push 自動 bump patch 版本marketplace.json 版本號有意義
Managed Settings推 SessionStart hook 給所有成員成員不需手動設定
wezoomtek plugin GitHub repo — 本文所有設定都在這個 repo 裡

---

✅ 先決條件:要讓這套跑起來你需要什麼

在開始設定之前,確認這幾項已就緒:

依賴最低需求驗證方式
Claude CodeTeams 方案claude --version
Plugin 已安裝(成員端)第一次需手動 /plugin install wez@wezoomtekls ~/.claude/plugins/installed_plugins.json
Marketplace clone 在本機~/.claude/plugins/marketplaces/wezoomtek/ls ~/.claude/plugins/marketplaces/wezoomtek/.claude-plugin/
Python 3腳本用 python3 解析 JSONpython3 --version
rsync腳本用 rsync 同步檔案which rsync
注意:成員第一次安裝 plugin 仍需手動執行 /plugin install wez@wezoomtek 本文講的「自動更新」是指「安裝後的版本追蹤」,不是首次安裝流程。

---

⚡ 設定一:把比對邏輯從 version string 改成 git SHA

把下面這支腳本存到你的 plugin repo,路徑是 scripts/update-wez-plugin.sh(同時會被部署到成員的 ~/.claude/plugins/marketplaces/wezoomtek/scripts/)。

#!/bin/bash
# Auto-update wez plugin: compares git SHA, any new commit triggers update

MARKETPLACE_DIR="$HOME/.claude/plugins/marketplaces/wezoomtek"
CACHE_BASE="$HOME/.claude/plugins/cache/wezoomtek/wez"
INSTALLED_JSON="$HOME/.claude/plugins/installed_plugins.json"

# Pull latest from remote
git -C "$MARKETPLACE_DIR" pull --quiet origin main > /dev/null 2>&1
if [ $? -ne 0 ]; then exit 0; fi

# Get latest SHA
REMOTE_SHA=$(git -C "$MARKETPLACE_DIR" rev-parse HEAD 2>/dev/null)
if [ -z "$REMOTE_SHA" ]; then exit 0; fi

# Get currently installed SHA
CURRENT_SHA=$(python3 -c "
import json, sys
try:
d = json.load(open('$INSTALLED_JSON'))
print(d['plugins']['wez@wezoomtek'][0].get('gitCommitSha', ''))
except Exception:
print('')
" 2>/dev/null)

# Exit if already up to date
if [ "$CURRENT_SHA" = "$REMOTE_SHA" ]; then exit 0; fi

# Read version from marketplace.json (for cache dir naming)
TARGET_VERSION=$(python3 -c "
import json
try:
d = json.load(open('$MARKETPLACE_DIR/.claude-plugin/marketplace.json'))
print(d['plugins'][0]['version'])
except Exception:
print('latest')
" 2>/dev/null)
if [ -z "$TARGET_VERSION" ]; then TARGET_VERSION="latest"; fi

# Each commit gets its own cache slot: version-SHA
SHORT_SHA="${REMOTE_SHA:0:8}"
TARGET_CACHE="$CACHE_BASE/${TARGET_VERSION}-${SHORT_SHA}"
mkdir -p "$TARGET_CACHE"
rsync -a --exclude='.git' "$MARKETPLACE_DIR/" "$TARGET_CACHE/" > /dev/null 2>&1

# Update installed_plugins.json
python3 - <<EOF
import json, datetime
path = "$INSTALLED_JSON"
with open(path) as f:
d = json.load(f)
entry = d['plugins']['wez@wezoomtek'][0]
entry['version'] = "$TARGET_VERSION"
entry['installPath'] = "$TARGET_CACHE"
entry['lastUpdated'] = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z')
entry['gitCommitSha'] = "$REMOTE_SHA"
with open(path, 'w') as f:
json.dump(d, f, indent=2, ensure_ascii=False)
EOF

關鍵改動在這兩行:

# 舊做法(version string)
if [ "$CURRENT_VERSION" = "$REMOTE_VERSION" ]; then exit 0; fi

# 新做法(git SHA)
if [ "$CURRENT_SHA" = "$REMOTE_SHA" ]; then exit 0; fi

另外,每次更新後把 gitCommitSha 寫回 installed_plugins.json——下次 session 才能正確比對。

---

⚙️ 設定二:GitHub Action 自動 bump patch 版本

有了 SHA 比對後,marketplace.json 的版本號已不是更新的判斷依據。但版本號還是有價值:方便人看(log、UI 顯示)。所以加一個 GitHub Action 在每次 push main 時自動 bump patch 版本,省去手動改的步驟。

在 plugin repo 建立 .github/workflows/bump-version.yml

name: Bump plugin version on push

on:
push:
branches: [main]
workflow_dispatch:

jobs:
bump:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Bump patch version in marketplace.json
run: |
FILE=".claude-plugin/marketplace.json"
CURRENT=$(python3 -c "import json; d=json.load(open('$FILE')); print(d['plugins'][0]['version'])")
MAJOR=$(echo $CURRENT | cut -d. -f1)
MINOR=$(echo $CURRENT | cut -d. -f2)
PATCH=$(echo $CURRENT | cut -d. -f3)
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
python3 -c "
import json
with open('$FILE') as f:
d = json.load(f)
d['plugins'][0]['version'] = '$NEW_VERSION'
with open('$FILE', 'w') as f:
json.dump(d, f, indent=2, ensure_ascii=False)
f.write('\n')
"
echo "Bumped $CURRENT → $NEW_VERSION"

- name: Commit version bump
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .claude-plugin/marketplace.json
git diff --cached --quiet || git commit -m "chore: bump plugin version [skip ci]"
git push

一個重要細節:commit message 結尾加 [skip ci] 這是防止 Action 觸發自己再跑一圈的標準做法。沒加的話,bump-version commit 會再觸發一次 bump-version,無限循環。

另外,不要加 paths-ignore。我踩過這個坑:加了 paths-ignore: ['.github/**'] 之後,pure version bump commit 沒有任何「受監控路徑」有異動,GitHub Actions 判定不需要跑,整個 workflow 靜音消失。移掉 paths-ignore、靠 [skip ci] 控流才是正確做法。

---

🔗 設定三:Managed Settings 推 SessionStart hook

前兩個設定讓更新腳本邏輯正確。但成員要自己在 ~/.claude/settings.json 加 SessionStart hook 才會被觸發——這等於又多了一個手動步驟。

Managed Settings 解決這個問題:Claude Code Teams 管理員可以把 settings 設定推給所有成員,成員端不需要做任何事。

在 Claude Code Teams 後台(admin console)的 Managed Settings 加入以下 JSON:

{
  "hooks": {
    "SessionStart": [
      {
        "type": "command",
        "command": "bash ~/.claude/plugins/marketplaces/wezoomtek/scripts/update-wez-plugin.sh > /dev/null 2>&1",
        "async": true
      }
    ]
  }
}

這段的邏輯:

  • SessionStart:每次開新 Claude Code session 時觸發
  • async: true:背景執行,不阻塞 session 啟動
  • > /dev/null 2>&1:靜默,不污染 session 輸出
  • 腳本路徑 ~/.claude/plugins/marketplaces/wezoomtek/scripts/update-wez-plugin.sh:這支腳本在 plugin 安裝時就已在成員機器上,不需要另外傳送
推上 Managed Settings 後,成員下次啟動 session 就會自動執行。如果版本沒變(SHA 相同),腳本在比對後立刻 exit 0,幾乎沒有啟動延遲。

---

🧪 驗收:怎麼確認這套真的有用

設定完成後,這樣驗收:

驗收步驟一:確認 installed_plugins.jsongitCommitSha

cat ~/.claude/plugins/installed_plugins.json | python3 -c "
import json, sys
d = json.load(sys.stdin)
print(json.dumps(d['plugins']['wez@wezoomtek'][0], indent=2))
"

正常輸出應包含:

{
  "version": "0.9.13",
  "gitCommitSha": "d6a837fcab0a0de0c6e8d07a94bb4afaf24b15ce",
  "lastUpdated": "2026-06-24T03:05:57.000Z"
}

驗收步驟二:模擬有新版本

installed_plugins.jsongitCommitSha 改成一個假值(如 aaaaaaaabbbbbbbbcccccccc),然後開新 session 或手動跑腳本:

bash ~/.claude/plugins/marketplaces/wezoomtek/scripts/update-wez-plugin.sh

跑完再查 installed_plugins.json,應該看到 SHA 更新回正確的 remote SHA 值。

驗收步驟三:確認 Managed Settings 有推到成員

在成員機器上查:

cat ~/.claude/settings.json | python3 -c "
import json, sys
d = json.load(sys.stdin)
hooks = d.get('hooks', {}).get('SessionStart', [])
print(json.dumps(hooks, indent=2))
"

看到有 update-wez-plugin.sh 那條就代表 Managed Settings 有生效。

---

❓ 常見問題 FAQ

Q:成員第一次安裝 plugin 還是要手動嗎?

是,第一次需要執行 /plugin install wez@wezoomtek。Managed Settings 管的是「已安裝後的自動更新」,不是首次安裝流程。後續所有更新都是自動的。

Q:Marketplace clone(~/.claude/plugins/marketplaces/wezoomtek/)是從哪來的?

執行 /plugin install wez@wezoomtek 時,Claude Code 會把你指定的 marketplace repo clone 到本機。這個 clone 就是後續 git pull 的來源。腳本用 git -C "$MARKETPLACE_DIR" pull 來拉最新版。

Q:[skip ci] 真的管用嗎?GitHub 說是用 actions/skip

[skip ci][ci skip]skip-checks: true 都是 GitHub 官方支援的跳過觸發機制,對 push event 有效。詳見 GitHub 文件

Q:這個腳本跑失敗會怎樣?

腳本在每個關鍵步驟後都有 exit 0git pull 失敗就退出、SHA 為空就退出)。失敗會靜默退出,不影響 session 啟動,成員不會感覺到任何異常——只是這次沒更新而已。下次 session 再試。

Q:每次 session 都要 git pull 會不會很慢?

git pull --quiet 加上遠端 repo 沒有變動的話,通常 1 秒內。加上 async: true,這個操作不在 session 啟動的關鍵路徑上,實際感受不到延遲。

---

🔗 延伸資源

author
陳彥彤

AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

support

覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。