From f7a1465354711bb16d737235a3e65ab7ec3e9fa0 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Fri, 21 Aug 2026 16:28:17 +0300 Subject: [PATCH 1/3] ci: call the registry dispatch from publish.yml instead of a trigger that cannot fire `dispatch-to-registry.yml` declared `release: [published]`, but the release is created inside publish.yml's github-release job by `gh release create` under `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events raised by GITHUB_TOKEN. The trigger was added on 2026-08-03 and 1.4.0 was the first release after it, so it had one chance and missed: the run history held nothing since 2026-08-03, and both entries there were workflow_dispatch and the retired push trigger. The 1.4.0 skill reached fluttersdk/ai only because it was dispatched by hand. The failure is silent, which is the part worth fixing. The publish workflow goes green either way and the only symptom is end users installing a skill one version behind. publish.yml now calls the workflow with `needs: github-release`. That removes the cross-workflow event and also sequences the dispatch after pub.dev has accepted the release rather than alongside it. The dead `release` trigger is replaced by `workflow_call`; `workflow_dispatch` stays as the manual hatch. Secrets are named rather than inherited: the called workflow needs exactly two, and handing it the whole store would sit badly in a repo that pins every action by SHA and runs zizmor over the result. Also fixes the version-extraction guard in the same file. It tested the ref against `^v[0-9]+\.[0-9]+\.[0-9]+`, but this repo tags without the prefix (publish.yml matches '[0-9]+.[0-9]+.[0-9]+*'), so that branch never matched a real tag and always fell through to pubspec.yaml. The fallback is correct on master after a release bump, which is why it went unnoticed. --- .github/workflows/dispatch-to-registry.yml | 29 +++++++++++++++++----- .github/workflows/publish.yml | 18 ++++++++++++++ CHANGELOG.md | 6 +++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dispatch-to-registry.yml b/.github/workflows/dispatch-to-registry.yml index fcaa903..5c4962a 100644 --- a/.github/workflows/dispatch-to-registry.yml +++ b/.github/workflows/dispatch-to-registry.yml @@ -1,8 +1,17 @@ # Auto-sync the FlutterSDK AI registry when a wind release is published. # -# Fires repository_dispatch at fluttersdk/ai on a published GitHub release, or -# on a manual run. The registry's sync.yml receives the event, pulls the -# skills/wind-ui/ subtree, bumps the version triplet, and pushes to main. +# Fires repository_dispatch at fluttersdk/ai. The registry's sync.yml receives +# the event, pulls the skills/wind-ui/ subtree, bumps the version triplet, and +# pushes to main. +# +# Called by publish.yml after its github-release job, so the dispatch happens +# only once pub.dev has actually accepted the release. It is NOT triggered by +# `release: [published]`, which is what it used to declare: publish.yml creates +# the release with `gh release create` under `GH_TOKEN: ${{ github.token }}`, +# and GitHub does not start workflow runs from events raised by GITHUB_TOKEN. +# That trigger could never fire, and did not fire once between the day it was +# added (2026-08-03) and the 1.4.0 release, which shipped a skill the registry +# never received until it was dispatched by hand. # # Deliberately NOT on every push. A skill edit that lands on master between # releases waits for the release that carries it, so the registry version @@ -18,8 +27,12 @@ name: Dispatch skill update to registry on: - release: - types: [published] + workflow_call: + secrets: + REGISTRY_BOT_APP_ID: + required: true + REGISTRY_BOT_PRIVATE_KEY: + required: true workflow_dispatch: permissions: @@ -37,7 +50,11 @@ jobs: id: ver run: | set -euo pipefail - if [[ -n "${GITHUB_REF_NAME}" && "${GITHUB_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + # `v?` because this repo tags WITHOUT the prefix: publish.yml + # matches '[0-9]+.[0-9]+.[0-9]+*' and CLAUDE.md says + # `git tag X.Y.Z`. The old `^v` test could never match a real + # tag here, so this always fell through to pubspec.yaml. + if [[ -n "${GITHUB_REF_NAME}" && "${GITHUB_REF_NAME}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+ ]]; then VER="${GITHUB_REF_NAME#v}" else VER=$(grep -E '^version:' pubspec.yaml | head -1 | awk '{print $2}') diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b0a2225..13c3eb2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -100,3 +100,21 @@ jobs: --notes "$NOTES" \ --verify-tag \ $PRERELEASE + + # Called here rather than triggered by `release: [published]`, which is what + # dispatch-to-registry.yml used to declare. The release above is created with + # `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs + # from events raised by GITHUB_TOKEN, so that trigger could never fire and + # never did: the 1.4.0 skill reached the registry only because it was + # dispatched by hand. Sequencing it after github-release also keeps the + # registry version tracking packages pub.dev actually accepted. + registry: + name: Sync skill to registry + needs: github-release + uses: ./.github/workflows/dispatch-to-registry.yml + # Named rather than `secrets: inherit`: the called workflow needs exactly + # these two, and this repo pins every action by SHA and runs zizmor over + # the result, so handing it the whole secret store would be out of step. + secrets: + REGISTRY_BOT_APP_ID: ${{ secrets.REGISTRY_BOT_APP_ID }} + REGISTRY_BOT_PRIVATE_KEY: ${{ secrets.REGISTRY_BOT_PRIVATE_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3dd0c..7f1ea4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. --- +## [Unreleased] + +### Quality + +- **The registry dispatch could never fire, so 1.4.0 shipped a skill the registry never received.** `dispatch-to-registry.yml` declared `release: [published]`, but the release is created inside `publish.yml`'s `github-release` job by `gh release create` running under `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events raised by `GITHUB_TOKEN`. The trigger was added on 2026-08-03 and 1.4.0 was the first release after it, so it had exactly one chance and missed: the run history showed nothing since 2026-08-03, both entries there being `workflow_dispatch` and the retired `push` trigger. It reached `fluttersdk/ai` only because it was dispatched by hand. The cost of this failure mode is that it is silent, since the publish workflow goes green either way and the only symptom is end users installing a skill a version behind. `publish.yml` now calls the workflow directly with `needs: github-release`, which removes the cross-workflow event entirely and also guarantees the dispatch happens after pub.dev has accepted the release rather than in parallel with it. The dead `release` trigger is gone and `workflow_call` replaces it; `workflow_dispatch` stays as the manual escape hatch. Secrets are passed by name rather than `secrets: inherit`, since the called workflow needs exactly two and this repo pins every action by SHA and runs zizmor over the result. Second bug in the same file, fixed alongside: the version-extraction step tested the ref against `^v[0-9]+\.[0-9]+\.[0-9]+`, but this repo tags without the `v` prefix, so that branch could never match a real tag and always fell through to reading `pubspec.yaml`. The fallback happens to be correct on master after a release bump, which is why nothing surfaced it. (`.github/workflows/dispatch-to-registry.yml`, `.github/workflows/publish.yml`) + ## [1.4.0] - 2026-08-21 ### Added From aeee3056f64ca8079aae77c91da1e086bbe1a43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C4=B1lcan=20=C3=87ak=C4=B1r?= Date: Fri, 21 Aug 2026 22:11:02 +0300 Subject: [PATCH 2/3] ci: keep a prerelease tag out of the registry sync Both notes from the review, verified against the files first. `publish.yml`'s tag filter is `[0-9]+.[0-9]+.[0-9]+*`, so `1.5.0-beta.1` matches it, and the extractor in the called workflow strips a `+build` suffix (`VER="${VER%%+*}"`) but nothing strips `-beta.1`. The registry would have received "1.5.0-beta.1" for a step whose job is to bump a version triplet. Until this branch the registry path could never execute at all, so this is the first release where it could happen, and this repo has shipped `1.0.0-alpha.*` tags. A branch name carrying a dash cannot be caught by the guard: on a `workflow_dispatch` the `github-release` job is skipped by its own tag check, and a dependant of a skipped job is skipped with it, so `registry` only evaluates the condition on a tag push. Noted at the guard so nobody has to re-derive it. The called workflow's checkout now sets `persist-credentials: false`, which the four other workflows here already do and zizmor's `artipacked` audit expects. The job only reads `pubspec.yaml`, so the token buys nothing, and this change is what puts the workflow inside the release pipeline and back under `zizmor SAST`. Both files parse (`pyyaml`), and the parsed shape is unchanged apart from the new `if` and the new `with` key. --- .github/workflows/dispatch-to-registry.yml | 6 ++++++ .github/workflows/publish.yml | 13 +++++++++++++ CHANGELOG.md | 2 ++ 3 files changed, 21 insertions(+) diff --git a/.github/workflows/dispatch-to-registry.yml b/.github/workflows/dispatch-to-registry.yml index 5c4962a..73ed227 100644 --- a/.github/workflows/dispatch-to-registry.yml +++ b/.github/workflows/dispatch-to-registry.yml @@ -45,6 +45,12 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 1 + # Every other checkout in this repo sets it, and zizmor's + # `artipacked` audit is on by default. This job only reads + # `pubspec.yaml`, so leaving the token in `.git/config` buys + # nothing and this workflow now runs inside the release + # pipeline. + persist-credentials: false - name: Extract version id: ver diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 13c3eb2..c755826 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -111,6 +111,19 @@ jobs: registry: name: Sync skill to registry needs: github-release + # Stable releases only. The tag filter above is `[0-9]+.[0-9]+.[0-9]+*`, so + # `1.5.0-beta.1` matches it, and the extractor in the called workflow strips + # a `+build` suffix but not a `-beta.1` one: the registry would receive + # "1.5.0-beta.1" for a step whose job is to bump a version triplet. This repo + # has shipped `1.0.0-alpha.*` tags before, and until this PR the registry + # path could never execute at all, so this is the first release where it + # could happen. + # + # A branch name carrying a dash cannot be caught by this: on a + # `workflow_dispatch` the `github-release` job is skipped by its own tag + # guard, and a dependant of a skipped job is skipped too, so `registry` only + # ever evaluates this on a tag push. + if: ${{ !contains(github.ref_name, '-') }} uses: ./.github/workflows/dispatch-to-registry.yml # Named rather than `secrets: inherit`: the called workflow needs exactly # these two, and this repo pins every action by SHA and runs zizmor over diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1ea4f..1a83ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. - **The registry dispatch could never fire, so 1.4.0 shipped a skill the registry never received.** `dispatch-to-registry.yml` declared `release: [published]`, but the release is created inside `publish.yml`'s `github-release` job by `gh release create` running under `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events raised by `GITHUB_TOKEN`. The trigger was added on 2026-08-03 and 1.4.0 was the first release after it, so it had exactly one chance and missed: the run history showed nothing since 2026-08-03, both entries there being `workflow_dispatch` and the retired `push` trigger. It reached `fluttersdk/ai` only because it was dispatched by hand. The cost of this failure mode is that it is silent, since the publish workflow goes green either way and the only symptom is end users installing a skill a version behind. `publish.yml` now calls the workflow directly with `needs: github-release`, which removes the cross-workflow event entirely and also guarantees the dispatch happens after pub.dev has accepted the release rather than in parallel with it. The dead `release` trigger is gone and `workflow_call` replaces it; `workflow_dispatch` stays as the manual escape hatch. Secrets are passed by name rather than `secrets: inherit`, since the called workflow needs exactly two and this repo pins every action by SHA and runs zizmor over the result. Second bug in the same file, fixed alongside: the version-extraction step tested the ref against `^v[0-9]+\.[0-9]+\.[0-9]+`, but this repo tags without the `v` prefix, so that branch could never match a real tag and always fell through to reading `pubspec.yaml`. The fallback happens to be correct on master after a release bump, which is why nothing surfaced it. (`.github/workflows/dispatch-to-registry.yml`, `.github/workflows/publish.yml`) +- **The registry sync now skips a prerelease tag, and its checkout stops carrying a token it does not use.** `publish.yml`'s tag filter is `[0-9]+.[0-9]+.[0-9]+*`, so `1.5.0-beta.1` matches it, and the version extractor strips a `+build` suffix but not a `-beta.1` one: the registry would have received "1.5.0-beta.1" for a step whose job is to bump a version triplet. That path could never execute before this change, which is why it has not bitten, and this repo has shipped `1.0.0-alpha.*` tags. A branch name containing a dash cannot trip the guard, because on a `workflow_dispatch` the `github-release` job is skipped by its own tag check and a dependant of a skipped job is skipped with it. Separately, the called workflow's checkout now sets `persist-credentials: false` like every other checkout here: the job only reads `pubspec.yaml`, zizmor's `artipacked` audit is on by default, and this change is what puts the workflow inside the release pipeline. (`.github/workflows/publish.yml`, `.github/workflows/dispatch-to-registry.yml`) + ## [1.4.0] - 2026-08-21 ### Added From 1c885ed7551e7aed4a53fd63e612d54fb888d666 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Fri, 21 Aug 2026 22:24:05 +0300 Subject: [PATCH 3/3] docs(changelog): split the entry per change and correct the prerelease reason The guard is right, the justification was not. Both the comment on publish.yml's registry job and the CHANGELOG claimed the registry "expects a version triplet" and would receive "1.5.0-beta.1" for a step whose job is to bump one. Measured against fluttersdk/ai's sync.yml, that is false twice over: it validates the upstream version as ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+)?$, which allows a prerelease on purpose, and it derives its own version by bumping its own manifest's patch number, so the string we send only ever reaches its release notes. The real reason to skip is distribution. A sync rsyncs skills/wind-ui/ onto the registry's main, which is what `npx skills add fluttersdk/ai` serves, so a beta tag would hand beta skill content to every consumer. Also splits the Unreleased section into one bullet per change, as CLAUDE.md section 4 requires: the dead trigger, the v-prefix extractor bug, the prerelease skip, and the checkout credential change were carried by two bullets. --- .github/workflows/publish.yml | 16 ++++++++++------ CHANGELOG.md | 8 ++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c755826..9a81f32 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -112,12 +112,16 @@ jobs: name: Sync skill to registry needs: github-release # Stable releases only. The tag filter above is `[0-9]+.[0-9]+.[0-9]+*`, so - # `1.5.0-beta.1` matches it, and the extractor in the called workflow strips - # a `+build` suffix but not a `-beta.1` one: the registry would receive - # "1.5.0-beta.1" for a step whose job is to bump a version triplet. This repo - # has shipped `1.0.0-alpha.*` tags before, and until this PR the registry - # path could never execute at all, so this is the first release where it - # could happen. + # `1.5.0-beta.1` matches it and this repo has shipped `1.0.0-alpha.*` tags + # before. The reason to skip is distribution, not a malformed version: the + # registry's `sync.yml` validates the upstream version as + # `^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+)?$` (a prerelease is explicitly + # allowed) and derives its own version by bumping its own manifest's patch + # number, so the string we send only reaches its release notes. What a sync + # does do is rsync `skills/wind-ui/` onto the registry's `main`, which is + # what `npx skills add fluttersdk/ai` serves. A beta tag would therefore + # hand beta skill content to every consumer, which is what a beta is meant + # not to do. # # A branch name carrying a dash cannot be caught by this: on a # `workflow_dispatch` the `github-release` job is skipped by its own tag diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a83ab2..04aa4fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,13 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. ### Quality -- **The registry dispatch could never fire, so 1.4.0 shipped a skill the registry never received.** `dispatch-to-registry.yml` declared `release: [published]`, but the release is created inside `publish.yml`'s `github-release` job by `gh release create` running under `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events raised by `GITHUB_TOKEN`. The trigger was added on 2026-08-03 and 1.4.0 was the first release after it, so it had exactly one chance and missed: the run history showed nothing since 2026-08-03, both entries there being `workflow_dispatch` and the retired `push` trigger. It reached `fluttersdk/ai` only because it was dispatched by hand. The cost of this failure mode is that it is silent, since the publish workflow goes green either way and the only symptom is end users installing a skill a version behind. `publish.yml` now calls the workflow directly with `needs: github-release`, which removes the cross-workflow event entirely and also guarantees the dispatch happens after pub.dev has accepted the release rather than in parallel with it. The dead `release` trigger is gone and `workflow_call` replaces it; `workflow_dispatch` stays as the manual escape hatch. Secrets are passed by name rather than `secrets: inherit`, since the called workflow needs exactly two and this repo pins every action by SHA and runs zizmor over the result. Second bug in the same file, fixed alongside: the version-extraction step tested the ref against `^v[0-9]+\.[0-9]+\.[0-9]+`, but this repo tags without the `v` prefix, so that branch could never match a real tag and always fell through to reading `pubspec.yaml`. The fallback happens to be correct on master after a release bump, which is why nothing surfaced it. (`.github/workflows/dispatch-to-registry.yml`, `.github/workflows/publish.yml`) +- **The registry dispatch could never fire, so 1.4.0 shipped a skill the registry never received.** `dispatch-to-registry.yml` declared `release: [published]`, but the release is created inside `publish.yml`'s `github-release` job by `gh release create` running under `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events raised by `GITHUB_TOKEN`. The trigger was added on 2026-08-03 and 1.4.0 was the first release after it, so it had exactly one chance and missed: the run history showed nothing since 2026-08-03, both entries there being `workflow_dispatch` and the retired `push` trigger. It reached `fluttersdk/ai` only because it was dispatched by hand. The cost of this failure mode is that it is silent, since the publish workflow goes green either way and the only symptom is end users installing a skill a version behind. `publish.yml` now calls the workflow directly with `needs: github-release`, which removes the cross-workflow event entirely and also guarantees the dispatch happens after pub.dev has accepted the release rather than in parallel with it. The dead `release` trigger is gone and `workflow_call` replaces it; `workflow_dispatch` stays as the manual escape hatch. Secrets are passed by name rather than `secrets: inherit`, since the called workflow needs exactly two and this repo pins every action by SHA and runs zizmor over the result. (`.github/workflows/dispatch-to-registry.yml`, `.github/workflows/publish.yml`) -- **The registry sync now skips a prerelease tag, and its checkout stops carrying a token it does not use.** `publish.yml`'s tag filter is `[0-9]+.[0-9]+.[0-9]+*`, so `1.5.0-beta.1` matches it, and the version extractor strips a `+build` suffix but not a `-beta.1` one: the registry would have received "1.5.0-beta.1" for a step whose job is to bump a version triplet. That path could never execute before this change, which is why it has not bitten, and this repo has shipped `1.0.0-alpha.*` tags. A branch name containing a dash cannot trip the guard, because on a `workflow_dispatch` the `github-release` job is skipped by its own tag check and a dependant of a skipped job is skipped with it. Separately, the called workflow's checkout now sets `persist-credentials: false` like every other checkout here: the job only reads `pubspec.yaml`, zizmor's `artipacked` audit is on by default, and this change is what puts the workflow inside the release pipeline. (`.github/workflows/publish.yml`, `.github/workflows/dispatch-to-registry.yml`) +- **The version extractor tested the ref for a `v` prefix this repo never tags with, so it always fell through to `pubspec.yaml`.** The step matched `^v[0-9]+\.[0-9]+\.[0-9]+`, while `publish.yml`'s tag filter is `[0-9]+.[0-9]+.[0-9]+*` and CLAUDE.md's release step is `git tag X.Y.Z`: that branch could not match a real tag here. Nothing surfaced it because the fallback happens to be correct on master after a release bump, so the dead branch and the working one returned the same string. The test is now `^v?[0-9]+\.[0-9]+\.[0-9]+`, which reads the tag when there is one and keeps the pubspec fallback for a manual run off a branch. (`.github/workflows/dispatch-to-registry.yml`) + +- **The registry sync now skips a prerelease tag, so a beta release does not hand beta skill content to every consumer.** `publish.yml`'s tag filter is `[0-9]+.[0-9]+.[0-9]+*`, so `1.5.0-beta.1` matches it and this repo has shipped `1.0.0-alpha.*` tags. The reason to skip is distribution rather than a malformed version: `fluttersdk/ai`'s `sync.yml` validates the upstream version as `^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+)?$` (a prerelease is explicitly allowed) and derives its own version by bumping its own manifest's patch number, so the string sent only reaches its release notes. What a sync does do is rsync `skills/wind-ui/` onto the registry's `main`, which is what `npx skills add fluttersdk/ai` serves. A branch name containing a dash cannot trip the guard, because on a `workflow_dispatch` the `github-release` job is skipped by its own tag check and a dependant of a skipped job is skipped with it. (`.github/workflows/publish.yml`) + +- **The called workflow's checkout stops carrying a token it does not use.** It now sets `persist-credentials: false` like every other checkout here: the job only reads `pubspec.yaml`, zizmor's `artipacked` audit is on by default, and this change is what puts the workflow inside the release pipeline. (`.github/workflows/dispatch-to-registry.yml`) ## [1.4.0] - 2026-08-21