From 9c186d780f5e450a831e30a0d59c42390fc94643 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 23:47:10 +0000 Subject: [PATCH 1/3] Clarify release.yml's version-tag guard for workflow_dispatch mis-fires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A manual workflow_dispatch re-run (added in a567c26 to backfill v1.0.0's missing bootloader/partitions/boot_app0 assets) must target the vX.Y.Z tag via the ref dropdown. Left on the default branch, GITHUB_REF_NAME is a branch name like "main" and the existing check failed with a misleading "bump version.h to match" message — that's exactly what happened re-running against v1.0.0 (dispatched against main by mistake). Now it calls out the wrong-ref mistake directly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LJFxsP371u76Ci5y2Wpf7U --- .github/workflows/release.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f53005..1cf1d74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,15 @@ jobs: # when hardware facts and code drift apart. - name: Verify version.h matches the release tag run: | + # A workflow_dispatch re-run (see the trigger comment above) must + # target the vX.Y.Z tag itself via the ref dropdown — left on the + # default branch, GITHUB_REF_NAME is a branch name like "main" and + # the check below fails with a misleading "bump version.h" message + # (seen 2026-09-03: dispatched against main instead of v1.0.0). + if [ "$GITHUB_REF_TYPE" != "tag" ]; then + echo "::error::This run's ref is the branch '${GITHUB_REF_NAME}', not a tag. Re-run via Actions -> Release -> Run workflow, and pick the vX.Y.Z tag from the Use workflow from dropdown before running." + exit 1 + fi header_version="$(sed -n 's/^#define FIRMWARE_VERSION "\(.*\)"/\1/p' src/version.h)" tag_version="${GITHUB_REF_NAME#v}" echo "version.h: '${header_version}' tag: '${tag_version}'" From ef64f4593ee442aa1557feb14cca2dfeb429cfe6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 23:49:57 +0000 Subject: [PATCH 2/3] Rework release.yml's workflow_dispatch to backfill by tag input, not ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub only allows workflow_dispatch against a ref whose own copy of the workflow file already defines the trigger — a tag cut before this trigger existed (v1.0.0) can never be dispatched against directly. Confirmed today trying exactly that: "Workflow does not have 'workflow_dispatch' trigger". The previous design assumed picking the tag from the ref dropdown would work; it doesn't and never could. Now workflow_dispatch takes an explicit `tag` input naming the release to attach built assets to, and always runs from a branch (main). Safe because "Verify version.h" still checks the checked-out source actually matches that tag's version before building or uploading anything — src/ is unchanged since v1.0.0 was cut, so building from main's tip produces the same firmware. Also scopes generate_release_notes off for the dispatch path: the action would otherwise regenerate v1.0.0's release notes from this run's own commit, silently pulling in every commit landed since instead of just backfilling the missing bootloader/partitions/boot_app0 assets. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LJFxsP371u76Ci5y2Wpf7U --- .github/workflows/release.yml | 77 +++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1cf1d74..4ea6552 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,14 +5,25 @@ on: tags: - "v*.*.*" # Lets an already-tagged release be rebuilt manually (Actions tab -> Release - # -> Run workflow -> pick the vX.Y.Z tag as the ref) to backfill assets a - # workflow change added after that release was first created — e.g. the - # bootloader/partitions/boot_app0 parts pages.yml needs, added after v1.0.0 - # had already shipped with only the app binary. `draft` below is - # conditioned on this vs. the tag-push trigger specifically so re-running - # it against an already-published release can't silently revert it to - # draft. + # -> Run workflow) to backfill assets a workflow change added after that + # release was first created — e.g. the bootloader/partitions/boot_app0 + # parts pages.yml needs, added after v1.0.0 had already shipped with only + # the app binary. This can't dispatch against the vX.Y.Z tag itself as the + # ref: GitHub only allows workflow_dispatch against a ref whose own copy of + # the workflow file already defines the trigger, and a tag cut before this + # trigger existed never will (confirmed 2026-09-03 trying exactly this + # against v1.0.0). So instead this always runs from a branch (main) and + # takes the target release tag as an input — safe only because the + # "Verify version.h" step below still checks the checked-out source + # actually matches that tag before anything is built or uploaded. + # `draft` is conditioned on workflow_dispatch vs. the tag-push trigger so + # re-running this against an already-published release can't silently + # revert it to draft. workflow_dispatch: + inputs: + tag: + description: "Existing release tag to attach the built assets to, e.g. v1.0.0" + required: true permissions: contents: write # needed to publish the release @@ -30,32 +41,37 @@ jobs: - name: Install PlatformIO run: pip install --upgrade platformio + - name: Resolve target release tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "value=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" + else + echo "value=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + fi + # docs/ROADMAP.md's Versioning section previously noted that a # tag/version.h mismatch was "a review-time catch, not an automated # one". It is automated now: shipping a binary whose boot banner # disagrees with its release tag makes every subsequent bug report # ambiguous, and that is exactly the cost this project keeps paying - # when hardware facts and code drift apart. - - name: Verify version.h matches the release tag + # when hardware facts and code drift apart. This is also the only + # thing standing between a workflow_dispatch mistake and the wrong + # binaries landing on an existing release, since dispatch builds + # whatever ref it's checked out against (usually main), not the + # tagged commit itself — see the trigger comment above. + - name: Verify version.h matches the target release tag run: | - # A workflow_dispatch re-run (see the trigger comment above) must - # target the vX.Y.Z tag itself via the ref dropdown — left on the - # default branch, GITHUB_REF_NAME is a branch name like "main" and - # the check below fails with a misleading "bump version.h" message - # (seen 2026-09-03: dispatched against main instead of v1.0.0). - if [ "$GITHUB_REF_TYPE" != "tag" ]; then - echo "::error::This run's ref is the branch '${GITHUB_REF_NAME}', not a tag. Re-run via Actions -> Release -> Run workflow, and pick the vX.Y.Z tag from the Use workflow from dropdown before running." - exit 1 - fi + target_tag="${{ steps.tag.outputs.value }}" header_version="$(sed -n 's/^#define FIRMWARE_VERSION "\(.*\)"/\1/p' src/version.h)" - tag_version="${GITHUB_REF_NAME#v}" - echo "version.h: '${header_version}' tag: '${tag_version}'" + tag_version="${target_tag#v}" + echo "version.h: '${header_version}' target tag: '${tag_version}'" if [ -z "$header_version" ]; then echo "::error::Could not parse FIRMWARE_VERSION from src/version.h" exit 1 fi if [ "$header_version" != "$tag_version" ]; then - echo "::error::src/version.h says ${header_version} but the tag is ${GITHUB_REF_NAME}. Bump version.h to match before tagging (docs/ROADMAP.md Versioning)." + echo "::error::src/version.h says ${header_version} but the target release tag is ${target_tag}. Checked-out ref must build the same firmware that tag represents (docs/ROADMAP.md Versioning)." exit 1 fi @@ -65,7 +81,7 @@ jobs: - name: Rename binary for Launcher / SD-drop distribution run: | mkdir -p dist - cp .pio/build/cardputer-adv/firmware.bin "dist/LoRaTraceRX-${GITHUB_REF_NAME}.bin" + cp .pio/build/cardputer-adv/firmware.bin "dist/LoRaTraceRX-${{ steps.tag.outputs.value }}.bin" # bootloader.bin/partitions.bin plus the Arduino core's boot_app0.bin # are the other three pieces a from-scratch flash needs beside the @@ -75,14 +91,21 @@ jobs: # (pages.yml) is what actually consumes them. - name: Collect flash parts for the web flasher run: | - cp .pio/build/cardputer-adv/bootloader.bin "dist/LoRaTraceRX-${GITHUB_REF_NAME}-bootloader.bin" - cp .pio/build/cardputer-adv/partitions.bin "dist/LoRaTraceRX-${GITHUB_REF_NAME}-partitions.bin" + cp .pio/build/cardputer-adv/bootloader.bin "dist/LoRaTraceRX-${{ steps.tag.outputs.value }}-bootloader.bin" + cp .pio/build/cardputer-adv/partitions.bin "dist/LoRaTraceRX-${{ steps.tag.outputs.value }}-partitions.bin" boot_app0="$(find ~/.platformio/packages/framework-arduinoespressif32 -name boot_app0.bin | head -n1)" - cp "$boot_app0" "dist/LoRaTraceRX-${GITHUB_REF_NAME}-boot_app0.bin" + cp "$boot_app0" "dist/LoRaTraceRX-${{ steps.tag.outputs.value }}-boot_app0.bin" + # generate_release_notes is off for a workflow_dispatch backfill: the + # action would otherwise regenerate the release body from this run's + # own commit (the dispatch branch's tip, not the original tag), which + # would rewrite the tag's real notes with every commit landed since — + # a dispatch run should only add the missing asset files, never touch + # the release's existing body/name. - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: dist/LoRaTraceRX-${{ github.ref_name }}*.bin + tag_name: ${{ steps.tag.outputs.value }} + files: dist/LoRaTraceRX-${{ steps.tag.outputs.value }}*.bin draft: ${{ github.event_name != 'workflow_dispatch' }} - generate_release_notes: true + generate_release_notes: ${{ github.event_name != 'workflow_dispatch' }} From 8d8e095495425f3e9a2f14ffac30f7fd4a0f632b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 23:57:44 +0000 Subject: [PATCH 3/3] Give the web flasher's raw .bin download links a real filename The stored file is the fixed name firmware.bin (copy_track() keeps it generic so manifest.json's part paths don't change between versions), and the links had no filename hint, so the browser was suggesting "firmware.bin" for every download regardless of which build it came from. Sets an explicit download="LoRaTraceRX-.bin" per track instead of renaming the stored file. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LJFxsP371u76Ci5y2Wpf7U --- scripts/build_web_flasher_site.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/build_web_flasher_site.py b/scripts/build_web_flasher_site.py index 48681c5..283df41 100644 --- a/scripts/build_web_flasher_site.py +++ b/scripts/build_web_flasher_site.py @@ -121,9 +121,20 @@ def main(): html = html.replace("__REPO_URL__", repo_url) html = html.replace("__BUILD_TIMESTAMP__", build_timestamp) + # The stored file is the fixed name "firmware.bin" (copy_track() above), + # kept generic on purpose so the manifest's part paths don't change + # between versions. Without an explicit `download` filename here the + # browser suggests that generic name instead of a real release name, so + # it's set explicitly per track rather than renaming the stored file. if has_stable: - html = html.replace('id="stable-bin-link" href="#"', 'id="stable-bin-link" href="./bin/stable/firmware.bin"') - html = html.replace('id="dev-bin-link" href="#"', 'id="dev-bin-link" href="./bin/dev/firmware.bin"') + html = html.replace( + 'id="stable-bin-link" href="#" download>', + f'id="stable-bin-link" href="./bin/stable/firmware.bin" download="LoRaTraceRX-{stable_version}.bin">', + ) + html = html.replace( + 'id="dev-bin-link" href="#" download>', + 'id="dev-bin-link" href="./bin/dev/firmware.bin" download="LoRaTraceRX-dev.bin">', + ) with open(os.path.join(out_dir, "index.html"), "w") as f: f.write(html)