Skip to content

ci: fail on main when SKILL.md's version has no release - #22

Merged
BenKalsky merged 6 commits into
mainfrom
ci/release-drift
Sep 12, 2026
Merged

BenKalsky merged 6 commits into
mainfrom
ci/release-drift

Conversation

@BenKalsky

Copy link
Copy Markdown
Member

The ClawHub publish workflow runs only on a published GitHub Release. A version bump merged to main without one leaves ClawHub serving the previous version, silently.

That is not hypothetical: hostinger-mcp (1.1.0 on main, 1.0.1 on ClawHub) and siteagent-elementor-studio (1.4.0 on main, 1.3.2 on ClawHub) each sat two months behind, and it surfaced because someone happened to look at the ClawHub UI — nothing in either repo said anything was wrong.

This check fails on main when SKILL.md's version has no matching release, so the gap is a red check instead of stale published content.

Deliberate limits

  • It enforces the mechanism, not the outcome. A skill published to ClawHub by hand still reads as drift here until a release exists. meta-ads-mcp turned out to be exactly that case — ClawHub already had 1.1.0 with no release behind it, which is also why "no release" is not a reliable staleness detector on its own. Requiring the release keeps the release notes as the single place "what changed" is recorded.
  • It does not run on pull requests. The PR that bumps the version legitimately has no release yet; the release is cut after the merge. Running it on PRs would block the very change it exists to encourage.
  • Weekly schedule plus workflow_dispatch, so a repo that goes quiet still reports.

Verified

The version parse was tested against the real files before this was written, including the failure path:

wordpress-api-pro      -> 3.9.4
siteagent-elementor..  -> 1.4.0   (SKILL_DIR is files/)
missing file           -> ::error::no version found ... ; exit=1

The || true on that pipeline is load-bearing: under set -euo pipefail a non-matching grep aborts the step before the error message can explain why.

🤖 Generated with Claude Code

The ClawHub publish workflow runs only on a published GitHub Release, so a
version bump merged to main without one leaves ClawHub serving the previous
version - silently. hostinger-mcp and siteagent-elementor-studio each sat two
months behind that way, and it surfaced by chance in the ClawHub UI rather
than from anything in this repo.

The check fails on main when SKILL.md's version has no matching release, so
the gap is a red check instead of stale published content. It deliberately
enforces the mechanism rather than the outcome: a skill published by hand
still reads as drift until a release exists, because the release notes are
where 'what changed' is supposed to live.

It does not run on pull requests - the PR that bumps the version legitimately
has no release yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-12T23:17:35.104519Z 52924c4 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@BenKalsky

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98e5c49294

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release-drift.yml Outdated
echo "::error::No 'version:' found in $SKILL_DIR/SKILL.md"
exit 1
fi
if gh release view "v$VERSION" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject draft releases when checking drift

When a matching tag belongs to a draft release, gh release view still succeeds, so this marks the version as released even though .github/workflows/publish-clawhub.yml runs only for release.types: [published] and ClawHub remains stale. The installed CLI documentation confirms that gh release view exposes the isDraft field, while gh release list requires --exclude-drafts to filter drafts; query --json isDraft/publishedAt here and only pass for a published release.

Useful? React with 👍 / 👎.

Two findings, each reported independently across the five repos carrying this
check.

A draft release passed it. 'gh release view' succeeds for a draft, but
publish-clawhub.yml triggers on 'release: published', so a draft turned the
check green while ClawHub kept serving the old version - exactly the failure
the check exists to catch. It now reads isDraft and fails with the command to
publish. A prerelease still passes: publishing one does fire the release event.

Publishing the release did not clear the red run. The triggers were push,
schedule and dispatch, and creating a release moves no branch - so main stayed
red until an unrelated push or the following Monday, which teaches people to
ignore the check. 'release: published' is now a trigger.

Verified against real releases: v3.9.4 -> isDraft false (pass), an absent tag
-> missing (fail).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

Both fixed in the latest commit. Five reviews ran in parallel across the repos carrying this check and converged on the same two problems, which is a fair signal they are real.

Draft releases passed. gh release view succeeds for a draft, but publish-clawhub.yml triggers on release: published — so a draft turned this check green while ClawHub kept serving the old version, which is precisely the failure it exists to catch. It now reads isDraft and fails with the command to publish. A prerelease still passes deliberately: publishing one does fire the release event.

Publishing the release did not clear the red run. The triggers were push, schedule and dispatch, and creating a release moves no branch — so main would have stayed red until an unrelated push or the following Monday. A check that stays red after you have done the thing it asked for is a check people learn to ignore, which would have made it worse than nothing. release: published is now a trigger.

Verified against real releases rather than reasoned about:

v3.9.4 (published) -> isDraft=false -> pass
v9.9.9 (absent)    -> missing       -> fail

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ead6292696

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

env:
SKILL_DIR: "wordpress-api-pro"
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check out main for release-triggered drift checks

When a draft is published after main has advanced beyond the release tag (or any older release is published), the default checkout for a release event uses that tag's commit rather than current main. The job can therefore validate the old commit's SKILL.md and pass on the tag SHA while the failed check on the latest main SHA remains red—the exact recovery path this trigger is intended to provide. Set the checkout ref to the default branch for release-triggered runs so the newly published release is compared against the current version on main.

Useful? React with 👍 / 👎.

Three findings from the second review round.

A release-triggered run checked out the TAG, so publishing an older draft - or
publishing after main had moved on - read that tag's SKILL.md and reported on
a version nobody was asking about. The checkout is pinned to main, which is
what the invariant is actually about.

'|| echo missing' classified every nonzero exit as 'no release exists', so a
transient API, network or auth failure would have told someone to cut a
release that already exists. The three cases are now distinct: published,
absent (404), and lookup failure (anything else, which fails loudly with the
error attached).

Verified against the live API rather than reasoned about:
  v3.9.4 -> draft=false         -> pass
  v9.9.9 -> 404                 -> fail, cut the release
  bad token -> HTTP 401         -> fail as a lookup error, not as missing

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

Pushed two more fixes, found in the parallel reviews of this same workflow in the sibling repos — worth applying here even where this PR's own round came back clean, since the file is meant to stay identical across them.

The release-triggered run checked out the tag, not main. Publishing an older draft, or publishing after main has advanced, would read that tag's SKILL.md and report on a version nobody is asking about. The checkout is pinned to main now — that is what the invariant is about.

|| echo missing swallowed lookup failures. A transient API, network or auth error was classified as "no release exists", which would have sent someone to cut a release that already exists. Three distinct states now, verified against the live API:

v3.9.4    -> draft=false   -> pass
v9.9.9    -> HTTP 404      -> fail: cut the release
bad token -> HTTP 401      -> fail as a LOOKUP ERROR, with the error attached

(The workflow_run trigger added in cloudways-mcp is deliberately NOT here: that repo publishes its release from a workflow using GITHUB_TOKEN, which suppresses the release event. This repo cuts releases by hand, so release: published fires normally.)

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 9ab9d8024e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

On a release event GITHUB_SHA is the TAGGED commit, so a release published
after main has advanced produces a green run attached to the tag's commit -
which cannot replace the red check sitting on main's head. The ref: main
checkout fixes which FILES are read; it cannot move the run's own SHA.

Closing that exactly would mean re-dispatching the workflow against main, and
the repository GITHUB_TOKEN is precisely what GitHub suppresses to prevent
recursion - the same rule behind the cloudways-mcp finding. A stored PAT in
five repos is a worse trade than a check that self-heals, so the schedule
moves from weekly to daily and the residual is written down where the next
reader will see it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

Correct, and the distinction is one I had blurred: ref: main fixes which files are read, it cannot move the run's own SHA. On a release event GITHUB_SHA is the tagged commit, so a green run lands on the tag and the red check stays on main's head.

I did not close it exactly, and want to be plain about why. Doing so means re-dispatching this workflow against main, and the repository GITHUB_TOKEN is precisely what GitHub suppresses to prevent recursion — the same rule behind the cloudways-mcp finding earlier in this round. That leaves a stored PAT across five repos, which buys a faster clear of a cosmetic red check at the cost of a real credential in five places. That is a worse trade.

What I did instead: the schedule moves from weekly to daily, so the residual self-heals within a day rather than up to seven, and the whole reasoning is written into the workflow where the next reader meets it rather than living in this thread.

The residual, stated precisely: publishing a release after main has advanced past its tag leaves a red Release drift check on main's head until the next push or the daily run. The check is wrong in that window; the published state is correct.

If you would rather have it exact, the PAT route is a deliberate call for a human to make, not something I will add unasked.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: f935cd4204

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

I got the underlying fact wrong and the decision followed it: GitHub's
recursion suppression for GITHUB_TOKEN-triggered events explicitly EXEMPTS
workflow_dispatch and repository_dispatch. A self-dispatch needs no stored
credential, so the trade I described - a PAT in five repos versus a check that
self-heals in a day - was not the trade on offer.

A release or workflow_run event now re-dispatches this workflow against main
instead of checking anything itself, and that run reports on main's head
commit, which is where the red check sits. The two jobs guard on event name so
they cannot call each other forever. The daily schedule stays as belt and
braces for a release published from a path that emits no event at all.

cloudways-mcp also moves to actions/checkout@v7, matching the four other
checkouts in that repo; the remaining repos stay on v4, which is theirs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

You are right, and the correction goes deeper than the fix: I had the underlying fact wrong, and my decision followed the wrong fact.

GitHub's recursion suppression for GITHUB_TOKEN-triggered events explicitly exempts workflow_dispatch and repository_dispatch. I checked the documentation rather than take it on trust, and the exemption is there. So the trade I argued for in the last round — a stored PAT across five repos versus a check that self-heals within a day — was never the trade on offer. There was no credential cost to weigh.

A release or workflow_run event now re-dispatches this workflow against main instead of checking anything itself, and that run reports on main's head commit, which is where the red check actually sits. The two jobs guard on github.event_name, which is what stops them calling each other forever. The daily schedule stays as belt and braces for a release published from a path that emits no event at all.

Worth naming the pattern, since it has now happened twice in this PR: I reasoned from a plausible-sounding platform rule instead of checking it, and then wrote the conclusion into a comment where it would have outlived the mistake. The workflow comments now state the exemption explicitly, so the next reader inherits the fact rather than my inference.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: a71ee51110

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

A published v$VERSION was not enough. The TAG can point at an older commit -
a draft cut before the bump merged - and publish-clawhub.yml checks out the
TAGGED commit and reads ITS SKILL.md, so it would publish the older version
(or fail as 'already exists') while main stayed unpublished and this check
stayed green. That is the false green this whole workflow exists to prevent,
reintroduced one layer down. The check now reads SKILL.md at the tag and
requires it to match main.

Verified against real tags: v3.9.4 carries 3.9.4 (passes), v3.9.0 carries
3.9.0 against a main at 3.9.4 (now fails, previously passed).

The jobs also tested github.event_name == 'workflow_run' in repos whose on:
block never subscribes to it - dead code that read as a capability. It stays
only in cloudways-mcp, which has the trigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@BenKalsky

Copy link
Copy Markdown
Member Author

Both fixed in the latest commit, and the P1 is the one that matters.

A published v$VERSION was not enough. The tag can point at an older commit — a draft cut before the bump merged — and publish-clawhub.yml checks out the tagged commit and reads its SKILL.md. So it would publish the older version, or fail as "already exists", while main stayed unpublished and this check sat green. That is exactly the false green this workflow exists to prevent, reintroduced one layer down.

It now reads SKILL.md at the tag and requires it to match main's. Verified against real tags rather than reasoned about:

v3.9.4 carries 3.9.4   -> passes
v3.9.0 carries 3.9.0   -> against main at 3.9.4, now FAILS (previously passed)

The dead event test is gone from the four repos whose on: block never subscribes to workflow_run. You are right that it read as a capability that did not exist; it stays only in cloudways-mcp, which has the trigger because it publishes releases from a workflow.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 52924c448e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@BenKalsky
BenKalsky merged commit 5e72751 into main Sep 12, 2026
3 checks passed
@BenKalsky
BenKalsky deleted the ci/release-drift branch September 12, 2026 23:18
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