Skip to content

Commit 2296106

Browse files
George-iamclaude
andcommitted
rework: replace auto-marketplace-PR with scheduled channel-health checks
The previous commit on this branch added a CI job that auto-opens a SHA-bump PR to anthropics/claude-plugins-community. Reality check (2026-06-11): that repo is a READ-ONLY MIRROR — a bot auto-closed our manual PR #63 within minutes, and the official docs state Anthropic's own pipeline bumps approved plugins' pins automatically on plugin-repo pushes, with a nightly catalog sync. An auto-PR job would just generate auto-closed noise. Reverted. What CAN be owned on our side is detection — the June postmortem's real gap was that three channels drifted for weeks with all-green pipelines. New channel-health.yml (Mon+Thu cron + manual dispatch) verifies, from the user's perspective: 1. npm latest == newest v* release 2. Open VSX latest == newest extension-v* release 3. plugin-repo manifest == newest v* release 4. marketplace SHA pin == plugin-repo HEAD (48h grace for Anthropic's auto-bump + nightly sync) On drift: opens/updates a tracking issue and fails the run. release.sh postflight + header updated to describe the real pin process (auto-bump + nightly sync + 48h grace + escalation via clau.de/plugin-directory-submission) instead of instructing a PR that gets auto-closed. Downgraded that postflight finding from err to warn since lag <48h is expected behavior. YAML validated; both shell payloads pass bash -n. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7c77c90 commit 2296106

3 files changed

Lines changed: 124 additions & 93 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: channel-health
2+
3+
# End-to-end distribution-channel verification, from the USER's side.
4+
#
5+
# June 2026 postmortem: every internal pipeline was green while three
6+
# channels were silently degraded for weeks — the community-marketplace
7+
# pin served April's v0.2.9 to new plugin installs, auto-update was dead
8+
# for existing binary users, and nobody noticed because nothing checked
9+
# what a fresh user actually receives. This workflow is that check.
10+
#
11+
# Channels verified against the newest git release tags:
12+
# 1. npm latest == newest v* release
13+
# 2. Open VSX latest == newest extension-v* release
14+
# 3. plugin-repo plugin.json == newest v* release
15+
# 4. community-marketplace SHA pin == plugin-repo HEAD
16+
# (the pin is bumped by ANTHROPIC's pipeline on our plugin-repo
17+
# pushes and the public catalog syncs nightly — so a 48h grace
18+
# period applies before we alert; direct PRs to that repo are
19+
# auto-closed, it is a read-only mirror)
20+
#
21+
# On any drift: opens (or comments on) a tracking issue and fails the run.
22+
23+
on:
24+
schedule:
25+
- cron: "0 9 * * 1,4" # Mon + Thu 09:00 UTC
26+
workflow_dispatch:
27+
28+
jobs:
29+
check:
30+
runs-on: ubuntu-latest
31+
permissions:
32+
contents: read
33+
issues: write
34+
steps:
35+
- name: Verify every channel against the newest release tags
36+
env:
37+
GH_TOKEN: ${{ github.token }}
38+
run: |
39+
set -uo pipefail
40+
problems=()
41+
42+
tags="$(gh api 'repos/AxmeAI/axme-code/releases?per_page=30' --jq '.[].tag_name')"
43+
cli_tag="$(echo "$tags" | grep -E '^v[0-9]' | head -1 || true)"
44+
ext_tag="$(echo "$tags" | grep -E '^extension-v[0-9]' | head -1 || true)"
45+
if [ -z "$cli_tag" ]; then
46+
problems+=("no \`v*\` tag in the releases list — install.sh and auto-update cannot resolve a version")
47+
fi
48+
cli_ver="${cli_tag#v}"
49+
ext_ver="${ext_tag#extension-v}"
50+
echo "Newest releases: CLI ${cli_tag:-NONE}, extension ${ext_tag:-NONE}"
51+
52+
# 1. npm
53+
npm_ver="$(npm view @axme/code version 2>/dev/null || echo FETCH_FAILED)"
54+
if [ -n "$cli_tag" ] && [ "$npm_ver" != "$cli_ver" ]; then
55+
problems+=("npm latest is \`$npm_ver\`, newest CLI release is \`$cli_ver\`")
56+
fi
57+
58+
# 2. Open VSX
59+
ovsx_ver="$(curl -fsSL https://open-vsx.org/api/AxmeAI/axme-code 2>/dev/null | jq -r '.version // "FETCH_FAILED"' || echo FETCH_FAILED)"
60+
if [ -n "$ext_tag" ] && [ "$ovsx_ver" != "$ext_ver" ]; then
61+
problems+=("Open VSX latest is \`$ovsx_ver\`, newest extension release is \`$ext_ver\`")
62+
fi
63+
64+
# 3. Plugin repo sync
65+
plugin_ver="$(gh api repos/AxmeAI/axme-code-plugin/contents/.claude-plugin/plugin.json --jq '.content' 2>/dev/null | base64 -d | jq -r '.version' || echo FETCH_FAILED)"
66+
if [ -n "$cli_tag" ] && [ "$plugin_ver" != "$cli_ver" ]; then
67+
problems+=("plugin-repo plugin.json is \`$plugin_ver\`, newest CLI release is \`$cli_ver\`")
68+
fi
69+
70+
# 4. Community marketplace pin (48h grace for Anthropic's pipeline)
71+
pin="$(curl -fsSL https://raw.githubusercontent.com/anthropics/claude-plugins-community/main/.claude-plugin/marketplace.json 2>/dev/null \
72+
| jq -r '.plugins[] | select(.name == "axme-code") | .source.sha' || echo FETCH_FAILED)"
73+
head_json="$(gh api repos/AxmeAI/axme-code-plugin/commits/main 2>/dev/null || echo '{}')"
74+
head_sha="$(echo "$head_json" | jq -r '.sha // "FETCH_FAILED"')"
75+
if [ "$pin" != "$head_sha" ]; then
76+
head_date="$(echo "$head_json" | jq -r '.commit.committer.date // empty')"
77+
head_age_h=999
78+
if [ -n "$head_date" ]; then
79+
head_age_h=$(( ( $(date +%s) - $(date -d "$head_date" +%s) ) / 3600 ))
80+
fi
81+
if [ "$head_age_h" -ge 48 ]; then
82+
problems+=("community-marketplace pin \`${pin:0:7}\` ≠ plugin-repo HEAD \`${head_sha:0:7}\` and HEAD is ${head_age_h}h old — Anthropic's auto-bump / nightly sync has not picked it up. Direct PRs are auto-closed (read-only mirror); escalate via https://clau.de/plugin-directory-submission")
83+
else
84+
echo "marketplace pin behind HEAD, but HEAD is only ${head_age_h}h old — inside the nightly-sync grace period"
85+
fi
86+
else
87+
echo "marketplace pin current: ${pin:0:7}"
88+
fi
89+
90+
# --- Report ---
91+
if [ "${#problems[@]}" -gt 0 ]; then
92+
body="$(printf -- '- %s\n' "${problems[@]}")"
93+
echo "::error::Channel drift detected"
94+
echo "$body"
95+
title="Channel health: distribution drift detected"
96+
existing="$(gh issue list --repo AxmeAI/axme-code --state open --search "in:title \"$title\"" --json number --jq '.[0].number // empty')"
97+
if [ -n "$existing" ]; then
98+
gh issue comment "$existing" --repo AxmeAI/axme-code --body "$(printf 'Still failing as of %s:\n\n%s' "$(date -u +%F)" "$body")"
99+
else
100+
gh issue create --repo AxmeAI/axme-code \
101+
--title "$title" \
102+
--body "$(printf 'Automated channel-health check (%s) found drift between what CI released and what users actually receive:\n\n%s\n\nRunbook: see the header comments of scripts/release.sh and .github/workflows/channel-health.yml.' "$(date -u +%F)" "$body")"
103+
fi
104+
exit 1
105+
fi
106+
echo "All channels healthy: CLI ${cli_tag:-n/a} (npm ✓, plugin repo ✓), extension ${ext_tag:-n/a} (Open VSX ✓), marketplace pin current or in grace."

.github/workflows/release-binary.yml

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -196,86 +196,3 @@ jobs:
196196
git add -A
197197
git commit -m "sync: ${GITHUB_REF_NAME} from axme-code" || echo "No changes to commit"
198198
git push
199-
200-
open-marketplace-pr:
201-
# Final mile of the Claude Code plugin channel. `claude plugin install
202-
# axme-code@claude-community` installs the commit SHA pinned in
203-
# anthropics/claude-plugins-community — NOT our plugin repo HEAD. Through
204-
# April–June 2026 that pin silently stayed at v0.2.9 because bumping it
205-
# was a manual PR that no process owned. This job opens (or updates) the
206-
# bump PR automatically after every successful sync. Anthropic
207-
# maintainers still review and merge it — that part is their
208-
# supply-chain gate and cannot (and should not) be automated away.
209-
#
210-
# Idempotency: a single standing branch `bump-axme-code` on the AxmeAI
211-
# fork is force-pushed each release. If a bump PR is already open, the
212-
# push updates it in place; otherwise a new PR is created. Consecutive
213-
# releases before a merge therefore never stack duplicate PRs.
214-
#
215-
# Token: needs push access to the AxmeAI/claude-plugins-community fork
216-
# AND permission to open PRs on the public upstream — a classic PAT
217-
# with `public_repo`/`repo` scope (fine-grained PATs cannot create PRs
218-
# on repos outside their owner). Falls back to PLUGIN_REPO_TOKEN; if
219-
# that one is fine-grained to axme-code-plugin only, add a
220-
# MARKETPLACE_PR_TOKEN secret.
221-
needs: sync-plugin-repo
222-
runs-on: ubuntu-latest
223-
permissions:
224-
contents: read
225-
steps:
226-
- name: Open or update marketplace bump PR
227-
env:
228-
GH_TOKEN: ${{ secrets.MARKETPLACE_PR_TOKEN || secrets.PLUGIN_REPO_TOKEN }}
229-
run: |
230-
set -euo pipefail
231-
version="${GITHUB_REF_NAME#v}"
232-
new_sha="$(gh api repos/AxmeAI/axme-code-plugin/commits/main --jq .sha)"
233-
234-
old_sha="$(curl -fsSL https://raw.githubusercontent.com/anthropics/claude-plugins-community/main/.claude-plugin/marketplace.json \
235-
| jq -r '.plugins[] | select(.name == "axme-code") | .source.sha')"
236-
if [ -z "$old_sha" ] || [ "$old_sha" = "null" ]; then
237-
echo "::error::Could not read the current axme-code pin from upstream marketplace.json"
238-
exit 1
239-
fi
240-
if [ "$old_sha" = "$new_sha" ]; then
241-
echo "Marketplace pin already current ($new_sha) — nothing to do."
242-
exit 0
243-
fi
244-
245-
# Branch off upstream main so the PR diff is exactly one line,
246-
# regardless of how stale our fork's main is.
247-
git clone --depth 1 "https://x-access-token:${GH_TOKEN}@github.com/AxmeAI/claude-plugins-community.git" fork
248-
cd fork
249-
git fetch --depth 1 https://github.com/anthropics/claude-plugins-community.git main
250-
git checkout -B bump-axme-code FETCH_HEAD
251-
252-
python3 - "$old_sha" "$new_sha" <<'PY'
253-
import json, sys
254-
old, new = sys.argv[1], sys.argv[2]
255-
path = ".claude-plugin/marketplace.json"
256-
raw = open(path).read()
257-
count = raw.count(old)
258-
assert count == 1, f"expected pinned SHA exactly once in marketplace.json, found {count}"
259-
updated = raw.replace(old, new)
260-
entry = [p for p in json.loads(updated)["plugins"] if p["name"] == "axme-code"][0]
261-
assert entry["source"]["sha"] == new, "axme-code entry not updated correctly"
262-
open(path, "w").write(updated)
263-
print(f"pin {old[:7]} -> {new[:7]} OK")
264-
PY
265-
266-
git config user.name "github-actions"
267-
git config user.email "github-actions@github.com"
268-
git add .claude-plugin/marketplace.json
269-
git commit -m "Update axme-code to v${version}"
270-
git push -f origin bump-axme-code
271-
272-
open_prs="$(gh pr list --repo anthropics/claude-plugins-community \
273-
--head AxmeAI:bump-axme-code --state open --json number --jq 'length')"
274-
if [ "$open_prs" -gt 0 ]; then
275-
echo "Existing bump PR updated in place via force-push."
276-
else
277-
gh pr create --repo anthropics/claude-plugins-community \
278-
--base main --head AxmeAI:bump-axme-code \
279-
--title "Update axme-code to v${version}" \
280-
--body "$(printf 'Bumps the pinned SHA for **axme-code** to the v%s sync commit ([`%s`](https://github.com/AxmeAI/axme-code-plugin/commit/%s)) of AxmeAI/axme-code-plugin.\n\nOne-line diff — only the existing entry'"'"'s `source.sha` changes. Opened automatically by the axme-code release workflow; maintained by the AxmeAI org (plugin author).' "$version" "${new_sha:0:7}" "$new_sha")"
281-
fi

scripts/release.sh

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@
2020
# IMPORTANT — the Claude Code marketplace pins us by SHA:
2121
# `claude plugin install axme-code@claude-community` reads
2222
# anthropics/claude-plugins-community/.claude-plugin/marketplace.json,
23-
# which pins our plugin to a COMMIT SHA of AxmeAI/axme-code-plugin. Our
24-
# sync job updates the plugin repo, but new users keep getting the pinned
25-
# SHA until someone opens a PR to claude-plugins-community bumping it.
23+
# which pins our plugin to a COMMIT SHA of AxmeAI/axme-code-plugin.
24+
# That repo is a READ-ONLY MIRROR — direct PRs are closed by a bot
25+
# (verified 2026-06-11, PR #63). Per the official docs
26+
# (code.claude.com/docs/en/plugins), ANTHROPIC's pipeline bumps the pin
27+
# automatically when we push new commits to the plugin repo, and the
28+
# public catalog syncs nightly — so allow 24-48h after a release.
2629
# (Discovered 2026-06-11: the pin still pointed at v0.2.9 from April —
27-
# every release since had been invisible to plugin installers.) The
28-
# postflight check below fails loudly until the marketplace PR lands.
30+
# either their auto-bump postdates April or it is silently rejecting
31+
# our updates.) If the pin is still stale 48h after a release, escalate
32+
# via https://clau.de/plugin-directory-submission. The scheduled
33+
# channel-health workflow checks this twice a week and opens an issue.
2934
#
3035
# Why this exists:
3136
# The v0.2.7 release took ~5 retries because of drift between manual steps:
@@ -444,11 +449,14 @@ pinned_sha="$(curl -fsSL "https://raw.githubusercontent.com/anthropics/claude-pl
444449
if [ "$pinned_sha" = "$plugin_head" ] && [ "$pinned_sha" != "FETCH_FAILED" ]; then
445450
ok "marketplace pin is current ($pinned_sha)"
446451
else
447-
err "marketplace pins $pinned_sha but $PLUGIN_REPO main is $plugin_head"
448-
err " New plugin installs will keep getting the OLD version until this lands:"
449-
err " 1. Fork anthropics/claude-plugins-community"
450-
err " 2. In .claude-plugin/marketplace.json set axme-code .source.sha = $plugin_head"
451-
err " 3. Open a PR (their CI + maintainers review it)"
452+
warn "marketplace pins $pinned_sha but $PLUGIN_REPO main is $plugin_head"
453+
warn " New plugin installs keep getting the OLD version until the pin updates."
454+
warn " The pin is bumped by ANTHROPIC's pipeline (triggered by our plugin-repo"
455+
warn " pushes); the public catalog then syncs nightly — allow 24-48h."
456+
warn " Do NOT open a PR to anthropics/claude-plugins-community: it is a"
457+
warn " read-only mirror and a bot auto-closes PRs (verified 2026-06-11, #63)."
458+
warn " Still stale after 48h? Escalate: https://clau.de/plugin-directory-submission"
459+
warn " (channel-health.yml re-checks twice a week and opens an issue on drift)"
452460
fi
453461

454462
# --- Done ---

0 commit comments

Comments
 (0)