End-to-end checklist for publishing a new firmware release. Two workflows are involved:
-
Sanity check the working tree
git status
git log --oneline -3
Abort if there are unintended changes. The M5Unified submodule will always look dirty locally — that's the tools/apply-m5-patches.sh patch, ignore it.
-
Confirm version number with the user.
Look at the latest tag and propose the next bump (patch/minor):
git tag --list 'v*' | sort -V | tail -3
Do not pick the version yourself unless the user has already said it.
-
(Optional) syntax-check any HTML changes
If tools/settings.html or docs/*.html was touched in this release:
./script/check_html_js.sh
-
Push main, tag, and push the tag
git push origin main
git tag vX.Y.Z
git push origin vX.Y.Z
-
Watch the release build (~5 min)
sleep 15
gh run list --repo ciniml/stackchan-idf --limit 3
gh run watch <release-run-id> --repo ciniml/stackchan-idf --exit-status
-
Verify clean version stamp
gh run view <release-run-id> --repo ciniml/stackchan-idf --log | grep "App.*version"
Must show App "stackchan_idf" version: vX.Y.Z (no -dirty). If -dirty appears, version.txt pin step is broken — investigate.
-
Confirm the Release exists
gh release view vX.Y.Z --repo ciniml/stackchan-idf should show the ZIP attached.
-
Manually trigger pages.yml
GitHub's loop-prevention means the release-published event from GITHUB_TOKEN-created releases does not chain-trigger downstream workflows. Manual dispatch is required for every release.
Wait for the push to fully index on GitHub before dispatching, then
dispatch on main. A naked gh workflow run pages.yml right after a
push reads whatever main currently points to, which can be the
pre-push commit if indexing is still in flight — and because pages.yml
has concurrency.cancel-in-progress: false, the older-SHA dispatch
then finishes AFTER the push-triggered run and overwrites it with
stale content. Has bitten us before (see commit log around v0.6.0).
gh workflow run --ref <SHA> would pin the dispatch but the gh CLI
only accepts branch / tag names there ("No ref found for: "),
not raw commit SHAs. So the only reliable pattern is poll-until-
remote-matches-local, then dispatch on main.
LOCAL_SHA=$(git rev-parse HEAD)
for _ in $(seq 1 30); do
REMOTE_SHA=$(gh api repos/ciniml/stackchan-idf/commits/main --jq '.sha')
[ "$LOCAL_SHA" = "$REMOTE_SHA" ] && break
sleep 1
done
gh workflow run pages.yml --repo ciniml/stackchan-idf --ref main
sleep 5
gh run list --repo ciniml/stackchan-idf --workflow pages.yml --limit 1 \
--json headSha,databaseId --jq '.[0]'
-
Watch the pages deploy (~30 s)
sleep 5
gh run list --repo ciniml/stackchan-idf --workflow pages.yml --limit 1
gh run watch <pages-run-id> --repo ciniml/stackchan-idf --exit-status
In the log, look for [ok] vX.Y.Z -> _site/firmware/vX.Y.Z/firmware-vX.Y.Z.zip.
-
Live smoke-test
curl -sL https://ciniml.github.io/stackchan-idf/versions.json
Expect the new tag at the top of the JSON array. The 301 redirect to www.fugafuga.org/stackchan-idf/ is expected (custom domain CNAME); follow with curl -L. The ZIP itself:
curl -sL https://ciniml.github.io/stackchan-idf/firmware/vX.Y.Z/firmware-vX.Y.Z.zip -o /dev/null -w "size: %{size_download}\n"
Should be ~2.5 MB.
-
Report to user
Summarise:
- Tag pushed, release run id, pages run id
- Version stamp from log (must be clean)
- Live versions.json contents
- ZIP size
Not automatable from this skill — flag to the user that this manual step is recommended.