/beta-prerelease - OTGW-firmware Beta Prerelease Skill
SkillDev toolsPublish an OTGW-firmware beta prerelease — bump _VERSION_PRERELEASE, push to otgw-1.x.x, tag, and let CI build + publish the GitHub prerelease
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the /beta-prerelease - OTGW-firmware Beta Prerelease Skill skill
What this skill tells your AI
The instructions your AI receives, as published by rvdbreemen/otgw-firmware in .claude/skills/beta-prerelease/SKILL.md and read by ahel’s review.
Publish a single beta build to field testers in Discord #beta-testing. Lightweight, repeatable many times within one minor cycle. Does NOT merge to main, does NOT touch the stable _SEMVER_CORE.
Usage
/beta-prerelease
No arguments. Auto-detects the current _VERSION_PRERELEASE and increments it via bin/bump-prerelease.sh.
Token-efficiency rules (apply throughout all phases)
- P1 Build output: Pipe through
tee .tmp/build_beta.log | tail -5. Only read.tmp/build_beta.logif exit code != 0. - P2 Phase 3 reads: Use targeted
grep/sedto extract only the relevant section from each file. Never read README, CHANGELOG, or RELEASE_NOTES in full. - P3 Phase 3 writes: Edit each file and note "Updated." immediately. Do NOT keep all edited content in context — keep only the filename.
- P4 Known Traps: Summarized as 5 bullets below. Read
.github/workflows/beta-prerelease.ymlonly if a trap is actually hit. - P5 Phase 8 CI poll: Use
gh run watchinstead of opening a browser.
When to use
Run when a firmware change under src/OTGW-firmware/** or src/libraries/** is committed and ready for field testing. Do NOT use for docs-only commits or full releases to main (use /release <version> for those).
How this differs from /release
| Aspect | /release | /beta-prerelease |
|---|---|---|
| Target branch | dev then merge to main | otgw-1.x.x only |
Bumps _SEMVER_CORE | yes | no |
| GitHub release | stable, not prerelease | prerelease: true |
| Discord channels | #nederlandse-ondersteuning, #english-support | #beta-testing |
| Mandatory checkpoints | 2 | 1 (Discord announcement) |
Writing style rules
- Never use em dashes — use colons, periods, commas, or parentheses instead.
- All release text MUST be in English (international audience).
- No emojis in release notes or Discord posts.
Process
Phase 0: Prepare — clean state on otgw-1.x.x
The 1.x maintenance/LTS line lives in its own worktree. Run this skill FROM the
wt-otgw-1.x.x worktree. Do NOT git checkout otgw-1.x.x inside the dev tree:
the branch is already checked out in the worktree and the checkout will fail.
- Confirm the branch:
git rev-parse --abbrev-ref HEADmust printotgw-1.x.x. - Verify clean state:
git statusmust be clean (or only the firmware change about to be bumped). Thengit pull origin otgw-1.x.x. - Detect the latest prerelease tag:
git fetch --tags PREV_TAG=$(git tag --list 'v*-*.*' --sort=-v:refname | head -1) - Detect the latest public stable release (store as
LATEST_PUBLIC):LATEST_PUBLIC=$(gh release view --json tagName --jq '.tagName' 2>/dev/null \ || git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -v -- '-' | head -1) - Read current prerelease:
grep _VERSION_PRERELEASE src/OTGW-firmware/version.h
Phase 1: ADR validation
Skip by default. Only pause if the staged firmware change introduces a new architectural pattern, dependency, or NFR shift. Most beta cycles do not need this gate.
Phase 2: Bump prerelease
bin/bump-prerelease.sh
Prints the transition (e.g. beta.3 -> beta.4). Store the new value as NEW_PRERELEASE. The helper does NOT git-add — you stage in Phase 6.
Assemble the tag: TAG="v${SEMVER_CORE}-${NEW_PRERELEASE}"
Phase 3: Refresh README + CHANGELOG + RELEASE_NOTES (mandatory, P2, P3)
The GitHub Action reads these files at the tagged commit. Stale narrative at the tag = stale release page (Trap 1). Refresh immediately after the bump so NEW_PRERELEASE is known.
Staleness check (P2 — targeted extractions, not full reads):
# 1. Commits since last public release (the change set to account for)
git log --pretty=format:'%h %s' "${LATEST_PUBLIC}..HEAD" -- src/OTGW-firmware/ src/libraries/ docs/
# 2. Existing narrative — CHANGELOG [Unreleased] is the rolling beta log on the
# 1.x line. (The README "What's New in v<stable>" sections are refreshed at
# STABLE release, not per beta, and there is no RELEASE_NOTES_*-beta file.)
grep -A 35 "## \[Unreleased\]" CHANGELOG.md | head -40
Decision:
- Every commit subject since
LATEST_PUBLICappears under CHANGELOG## [Unreleased]→ pass silently, continue to Phase 4. - Any commit missing → refresh the CHANGELOG now. Stop and ask only if the gap is ambiguous; otherwise edit in-session.
Authoring rules (P3 — write immediately, keep only filename in context):
CHANGELOG.md— append under## [Unreleased]using Keep-a-Changelog headings (### Added/Changed/Fixed/Removed/Documentation). One bullet per change, with ADR/TASK/PR/GH-issue reference. → note "Updated." This is the only mandatory narrative for a 1.x beta.README.md— leave untouched. The 1.x README's "What's New in v" sections are refreshed at STABLE release (via/release), not per beta.RELEASE_NOTES_<next-stable>.md(e.g.RELEASE_NOTES_1.7.1.md) — optional during a beta cycle (authored in full at stable release). Update it now only if you keep a running draft. → note "Updated."
Skip the CHANGELOG edit only when this is a re-cut at the same change surface (previous tag hit Trap 2). Note the reason in the commit message.
Phase 4: Build verification (P1)
mkdir -p .tmp
python build.py --firmware 2>&1 | tee .tmp/build_beta.log | tail -5
echo "Exit: $?"
Must exit 0. On failure: read .tmp/build_beta.log for diagnosis, fix, retry. Do NOT push a tag on a broken build.
Phase 5: Evaluator
python evaluate.py --quick
Must show no new failures. Pre-existing baseline failures unrelated to this change: document in the commit message.
Phase 6: Commit and push to otgw-1.x.x
# The 1.x bin/bump-prerelease.sh updates version banners across ~24 files and
# does NOT auto-stage. Stage the real change set (on Windows, --ignore-cr-at-eol
# filters out EOL-only churn). On a clean Phase-0 tree this is exactly the bump
# banners + version.h + data/version.hash + your firmware change + CHANGELOG.md:
git add $(git diff --ignore-cr-at-eol --name-only)
git commit -m "chore(release): ${NEW_PRERELEASE}
<one-line summary of what is in this beta>"
git push origin otgw-1.x.x
If the pre-commit hook blocks: re-stage version.h + data/version.hash and retry. Do NOT bypass with OTGW_BUMP_HOOK_DISABLE=1.
Note: the .githooks/commit-msg task-hook requires a TASK-NNN whose backlog/tasks/task-NNN-*.md is tracked in THIS worktree. The backlog lives in the dev tree, so a 1.x firmware commit usually cannot satisfy it. Use a chore(release): ... subject (exempt from the task-hook) for the bump commit, or OTGW_TASK_HOOK_DISABLE=1 if you reference a cross-tree TASK-NNN.
Phase 7: Create and push the prerelease tag
SEMVER_CORE=$(grep '_SEMVER_CORE ' src/OTGW-firmware/version.h | awk -F'"' '{print $2}')
TAG="v${SEMVER_CORE}-${NEW_PRERELEASE}"
git tag -a "${TAG}" -m "Beta prerelease ${NEW_PRERELEASE}"
git push origin "${TAG}"
The push fires .github/workflows/beta-prerelease.yml. CI builds firmware + filesystem, creates a GitHub prerelease, uploads .ino.bin, .littlefs.bin, SHA256SUMS, flash scripts, and the flash-bundle zip.
Phase 8: Wait for the GitHub Action (P5)
gh run watch --exit-status
When it exits 0, verify the release:
gh release view "${TAG}" --json tagName,isPrerelease,assets \
--jq '{tag: .tagName, prerelease: .isPrerelease, assets: [.assets[].name]}'
Expected assets: *.ino.bin, *.littlefs.bin, SHA256SUMS, flash_otgw.sh, flash_otgw.bat, OTGW-firmware-*-flash-bundle.zip.
On failure: inspect logs with gh run view --log-failed, fix the issue, and re-run via workflow_dispatch or push a new tag.
Phase 9: Discord announcement (CHECKPOINT)
Prepare announcement for #beta-testing (channel ID 914498730001072149). Diff link points at LATEST_PUBLIC (testers want to see what changed since the last stable, not since a previous beta).
Beta ${NEW_PRERELEASE} is up.
Version: ${SEMVER_CORE}-${NEW_PRERELEASE}
What is new: <one or two sentences>
Download: https://github.com/rvdbreemen/OTGW-firmware/releases/tag/${TAG}
Diff vs ${LATEST_PUBLIC}: https://github.com/rvdbreemen/OTGW-firmware/compare/${LATEST_PUBLIC}...${TAG}
Changelog: https://github.com/rvdbreemen/OTGW-firmware/blob/${TAG}/CHANGELOG.md
Please flash and report findings here (good and bad).
CHECKPOINT: Show the announcement to the user before sending.
Dry-run (testing without publishing)
git checkout -b test/beta-prerelease-dryrun
git tag -a v0.0.0-beta.dryrun -m "dryrun" && git push origin v0.0.0-beta.dryrun
# watch the Action, then clean up:
gh release delete v0.0.0-beta.dryrun --yes
git push --delete origin v0.0.0-beta.dryrun && git tag -d v0.0.0-beta.dryrun
git checkout otgw-1.x.x && git branch -d test/beta-prerelease-dryrun
Known traps (P4 — full detail in beta-prerelease.yml)
- Trap 1: Immutable-releases locks publish — upload after
gh release createreturns HTTP 422. Workaround already in CI: draft-first, attach all assets, then flip--draft=false. If you hit this manually, re-tag under the next beta number. - Trap 2: Deleted immutable release reserves the tag forever — even after deletion, the tag cannot be reused. Bump the prerelease number and note the skipped tag in the commit message.
- Trap 3:
GITHUB_TOKENevents do not chain —release-assets.yml(triggered byrelease: published) does not fire when the release is created byGITHUB_TOKEN.beta-prerelease.ymlis therefore self-contained (generates SHA256SUMS + zip + flash scripts itself). - Trap 4: Diff link to previous beta hides what testers care about — always link vs
LATEST_PUBLIC(the non-prerelease "Latest" release), not the previous beta tag. - Trap 5: Stale narrative at tagged commit is permanent — Phase 3 refresh runs before the tag is pushed for this reason. Skipping Phase 3 = stale release page that cannot be edited after publish.
Important rules
- Never use em dashes in any generated text.
- Always push to remote after every commit.
- Never force-push to otgw-1.x.x.
- Build and evaluator gates are mandatory — do not push a tag if either is red.
- One checkpoint: the Discord announcement in Phase 9.
- Do NOT bypass the bump-check hook with
OTGW_BUMP_HOOK_DISABLE=1— if the hook blocks, you forgot to stageversion.h/data/version.hash.
Signals
- GitHub stars
- 203
- Forks
- 43
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
beta-prerelease- Source
- github.com/rvdbreemen/otgw-firmware