Skip to content

Commit fbe102c

Browse files
authored
Merge pull request #152 from AxmeAI/feat/auto-marketplace-pr-20260611
feat(ci): scheduled channel-health checks (user-perspective drift detection)
2 parents 5954c85 + 2296106 commit fbe102c

2 files changed

Lines changed: 124 additions & 10 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."

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)