ci(release): fix the Release workflow and attach the release files automatically #1044

Closed
hckrman101 wants to merge 1 commit from ci-automated-release into main
hckrman101 commented 2026-11-09 07:52:46 +00:00 (Migrated from github.com)

What was broken

The Release workflow has failed on every push to main that had something to release. .releaserc.json uses the conventionalcommits preset, but conventional-changelog-conventionalcommits was not in package.json, so semantic-release stopped in its first step with Cannot find module 'conventional-changelog-conventionalcommits' (for example run 42576776625). The runs that show as passing only got as far as "the local branch main is behind the remote one".

Even with the module in place, a release would not have produced files. The tag semantic-release pushes sits on the chore(release): ... [skip ci] commit, and when it is pushed with GITHUB_TOKEN it starts no workflow. So the Linux AppImage and Windows builder workflows, which attach the files, would never have run. Separately, the Windows builder's release job was off unless vars.CI_RELEASE_WINDOWS was 'true', which is why v2.0.7-rc.1 has only the AppImage and hookshot-linux.txt, with no HookshotBuilder.exe or hookshot-setup.txt.

How releases work with this PR

Trigger: unchanged. Every push to main (stable) or next (release candidates) runs semantic-release, which picks the version from Conventional Commits since the last v* tag, as docs/versioning.md describes. A tag pushed by hand still works as it does today.

  • package.json adds conventional-changelog-conventionalcommits 9.3.1 (the 9.x line matches commit-analyzer 13 and release-notes-generator 14; 10.x is a rewrite) and @semantic-release/github 12.0.10. A package-lock.json is added, and the workflow uses npm ci.
  • semantic-release now also makes the GitHub release, with its conventional-commits notes. A release from next is a prerelease. The GitHub plugin's issue and PR comments, labels and failure issues are turned off.
  • A second job in the Release workflow starts the Linux AppImage and Windows builder workflows on the new tag with workflow_dispatch, which GITHUB_TOKEN is allowed to do. On a tag ref they run exactly as on a pushed tag, so their existing build and release jobs are reused unchanged. Only that job has actions: write; the semantic-release job keeps contents: write, and everything else is read-only.
  • The files attached are the same as on v2.0.3 to v2.0.6, with the same names: Hookshot-<tag>-x86_64.AppImage, hookshot-linux.txt, HookshotBuilder.exe and hookshot-setup.txt. New parts go to the linux-parts and builder-parts releases as before. Past releases ship no separate checksum file, so none is added; the setup lists already name each part by its SHA-256.
  • The Windows builder's release job now runs on every tag unless vars.CI_RELEASE_WINDOWS is 'false'.
  • scripts/release_upload.sh marks any vX.Y.Z-<prerelease> tag as a prerelease, not "latest". Before, only tags containing -rc were. When the release already exists (made by semantic-release), it only adds files, and it replaces any that are already there (--clobber). Running the builds again on a tag therefore updates its files instead of failing. It also refuses file names that look like game data, dumps or NVIDIA runtime files (*.nso, *.nca, *romfs*, *nvngx*, *dlss* and so on), as a backstop.
  • docs/versioning.md is updated to match, and node_modules/ is added to .gitignore.

Testing

  • actionlint 1.7.7 with shellcheck 0.10.0 passes on the three workflows, shellcheck passes on scripts/release_upload.sh, and python -I -B tests/run_all.py reports 106 passed, 0 failed, 14 skipped.
  • I ran a local semantic-release --dry-run against a copy of current main with the new lockfile. It loads the preset, finds v2.0.6 and the commits since it, and computes 2.0.7 with notes.
  • Fork dry run 1, a hand-made tag: I pushed v0.0.0-test.1 to hckrman101/Hookshot and ran both build workflows on that tag with workflow_dispatch, the same call the Release workflow makes (https://github.com/hckrman101/Hookshot/actions/runs/42578234909 and https://github.com/hckrman101/Hookshot/actions/runs/42578265504). The release they made was a prerelease, not "latest", with GitHub's generated notes and hookshot-linux.txt, hookshot-setup.txt, Hookshot-v0.0.0-test.1-x86_64.AppImage and HookshotBuilder.exe attached. I then ran the Windows builder on the same tag again (https://github.com/hckrman101/Hookshot/actions/runs/42582057076). It replaced HookshotBuilder.exe and hookshot-setup.txt in place and did not fail. Its first attempt hit the 504 described below and passed on a re-run.
  • Fork dry run 2, the whole path: on a fork-only next branch, I added a seed tag v0.0.0 and a fix: commit, which changed only package.json's repository URL to the fork. The push ran the Release workflow (https://github.com/hckrman101/Hookshot/actions/runs/42580051369). semantic-release computed 0.0.1-rc.1, pushed the tag with GITHUB_TOKEN, made the prerelease with its conventional-commits notes, and wrote the tag output, and the second job dispatched both builds. The tag push itself started no workflow, so nothing ran twice. Both builds attached their four files to the prerelease (https://github.com/hckrman101/Hookshot/actions/runs/42580092068 and https://github.com/hckrman101/Hookshot/actions/runs/42580095042). Twice, the Windows run got GitHub 504s on all five retries while downloading upstream's releases/download/v2.0.7/hookshot-setup.txt (release_parts.py previous), and both times a re-run passed. That download is older than this PR. If it keeps happening, fetching the asset through the API (as setup.c does) would avoid it. A second Release run on the same commit made no release.
  • Afterwards I deleted the fork's test releases and tags (v0.0.0-test.1, v0.0.0, v0.0.1-rc.1, and the fork's builder-parts and linux-parts) and the next branch.

What a maintainer needs to know or do

  • Before merging, make the v2.0.7 tag reachable from main. v2.0.7 (and v2.0.7-rc.1) point at 5656b04, a hotfix commit that is not on main. semantic-release only sees tags merged into the branch, so on main it still finds v2.0.6 as the last release, computes 2.0.7 from the fix: commits since, commits the chore(release): 2.0.7 changelog entry to main, and then fails on git tag v2.0.7 because the tag already exists. I reproduced this locally. Merge 5656b04 into main first: it conflicts in scripts/linux/appimage.sh, where main has its own header fix. After that, the first Release run after the merge cuts v2.0.8 from those fix: commits and builds its files. Every later merge with a feat: or fix: commit makes a release too, which is what docs/versioning.md already describes.
  • No secret is required while main and next have no branch protection, because GITHUB_TOKEN can push the release commit. If you protect them later, add a RELEASE_TOKEN secret (a fine-grained PAT with Contents: read and write on this repository, allowed to bypass the protection). The workflow already prefers it when it is set.
  • Under Settings > Actions > General > Workflow permissions, the default can stay read-only, since the jobs request what they need. If an organization policy caps GITHUB_TOKEN at read, the Release workflow cannot push or dispatch.
  • To keep uploading the Windows setup by hand, set the repository variable CI_RELEASE_WINDOWS to false.
  • To attach a release's files again, run Linux AppImage and Windows builder on its tag (Actions tab, Run workflow, pick the tag; or gh workflow run linux-appimage.yml --ref <tag>).
  • If a token you add (RELEASE_TOKEN) ever does start the tag workflows on the release tag in addition to the dispatch, each build runs twice. Both runs are in the same concurrency group, so they queue one after the other, and the second replaces the files with the same build.

Which commits make a release

semantic-release only counts Conventional Commit subjects. It ignores merge commits, but it does read the commits a merge brings in. On main since v2.0.0, there are 332 commits: 128 merges, and of the 204 others, 66 are conventional, 21 of them feat/fix/perf. Most of those 21 come from contributors' PRs. Most of the maintainers' own subjects are prose ("Pipeline state list: ...", "Lift: ..."), and those neither trigger a release nor appear in the notes or changelog.txt. So with this fix, releases would happen whenever a contributor's fix: or feat: lands, and a run of prose-only changes would never release. If that does not match how you want to cut versions, use conventional subjects (fix(vk): ...), or squash-merge with a conventional PR title.

Alternative: release-please

release-please would replace semantic-release with a release PR that it keeps up to date. A maintainer merges that PR to cut each version. The changelog in the PR can be edited before merging, every release goes through a human, and no npm install is needed in CI. It reads the same Conventional Commits, so prose subjects would still be left out unless the release PR is edited or Release-As: is used. Its tag and release are made with GITHUB_TOKEN too, so it has the same caveat: it needs a RELEASE_TOKEN PAT for the tag to start the build workflows, or the build workflows dispatched or called from the same workflow, as this PR does. I kept semantic-release because it is what release.yml, .releaserc.json and docs/versioning.md already set up, but I can switch this PR to release-please if you prefer.

## What was broken The Release workflow has failed on every push to main that had something to release. `.releaserc.json` uses the `conventionalcommits` preset, but `conventional-changelog-conventionalcommits` was not in `package.json`, so semantic-release stopped in its first step with `Cannot find module 'conventional-changelog-conventionalcommits'` (for example run 42576776625). The runs that show as passing only got as far as "the local branch main is behind the remote one". Even with the module in place, a release would not have produced files. The tag semantic-release pushes sits on the `chore(release): ... [skip ci]` commit, and when it is pushed with `GITHUB_TOKEN` it starts no workflow. So the Linux AppImage and Windows builder workflows, which attach the files, would never have run. Separately, the Windows builder's release job was off unless `vars.CI_RELEASE_WINDOWS` was `'true'`, which is why v2.0.7-rc.1 has only the AppImage and `hookshot-linux.txt`, with no `HookshotBuilder.exe` or `hookshot-setup.txt`. ## How releases work with this PR Trigger: unchanged. Every push to `main` (stable) or `next` (release candidates) runs semantic-release, which picks the version from Conventional Commits since the last `v*` tag, as `docs/versioning.md` describes. A tag pushed by hand still works as it does today. - `package.json` adds `conventional-changelog-conventionalcommits` 9.3.1 (the 9.x line matches commit-analyzer 13 and release-notes-generator 14; 10.x is a rewrite) and `@semantic-release/github` 12.0.10. A `package-lock.json` is added, and the workflow uses `npm ci`. - semantic-release now also makes the GitHub release, with its conventional-commits notes. A release from `next` is a prerelease. The GitHub plugin's issue and PR comments, labels and failure issues are turned off. - A second job in the Release workflow starts the Linux AppImage and Windows builder workflows on the new tag with `workflow_dispatch`, which `GITHUB_TOKEN` is allowed to do. On a tag ref they run exactly as on a pushed tag, so their existing build and release jobs are reused unchanged. Only that job has `actions: write`; the semantic-release job keeps `contents: write`, and everything else is read-only. - The files attached are the same as on v2.0.3 to v2.0.6, with the same names: `Hookshot-<tag>-x86_64.AppImage`, `hookshot-linux.txt`, `HookshotBuilder.exe` and `hookshot-setup.txt`. New parts go to the `linux-parts` and `builder-parts` releases as before. Past releases ship no separate checksum file, so none is added; the setup lists already name each part by its SHA-256. - The Windows builder's release job now runs on every tag unless `vars.CI_RELEASE_WINDOWS` is `'false'`. - `scripts/release_upload.sh` marks any `vX.Y.Z-<prerelease>` tag as a prerelease, not "latest". Before, only tags containing `-rc` were. When the release already exists (made by semantic-release), it only adds files, and it replaces any that are already there (`--clobber`). Running the builds again on a tag therefore updates its files instead of failing. It also refuses file names that look like game data, dumps or NVIDIA runtime files (`*.nso`, `*.nca`, `*romfs*`, `*nvngx*`, `*dlss*` and so on), as a backstop. - `docs/versioning.md` is updated to match, and `node_modules/` is added to `.gitignore`. ## Testing - actionlint 1.7.7 with shellcheck 0.10.0 passes on the three workflows, shellcheck passes on `scripts/release_upload.sh`, and `python -I -B tests/run_all.py` reports 106 passed, 0 failed, 14 skipped. - I ran a local `semantic-release --dry-run` against a copy of current main with the new lockfile. It loads the preset, finds v2.0.6 and the commits since it, and computes 2.0.7 with notes. - Fork dry run 1, a hand-made tag: I pushed `v0.0.0-test.1` to hckrman101/Hookshot and ran both build workflows on that tag with `workflow_dispatch`, the same call the Release workflow makes (https://github.com/hckrman101/Hookshot/actions/runs/42578234909 and https://github.com/hckrman101/Hookshot/actions/runs/42578265504). The release they made was a prerelease, not "latest", with GitHub's generated notes and `hookshot-linux.txt`, `hookshot-setup.txt`, `Hookshot-v0.0.0-test.1-x86_64.AppImage` and `HookshotBuilder.exe` attached. I then ran the Windows builder on the same tag again (https://github.com/hckrman101/Hookshot/actions/runs/42582057076). It replaced `HookshotBuilder.exe` and `hookshot-setup.txt` in place and did not fail. Its first attempt hit the 504 described below and passed on a re-run. - Fork dry run 2, the whole path: on a fork-only `next` branch, I added a seed tag `v0.0.0` and a `fix:` commit, which changed only package.json's repository URL to the fork. The push ran the Release workflow (https://github.com/hckrman101/Hookshot/actions/runs/42580051369). semantic-release computed 0.0.1-rc.1, pushed the tag with `GITHUB_TOKEN`, made the prerelease with its conventional-commits notes, and wrote the tag output, and the second job dispatched both builds. The tag push itself started no workflow, so nothing ran twice. Both builds attached their four files to the prerelease (https://github.com/hckrman101/Hookshot/actions/runs/42580092068 and https://github.com/hckrman101/Hookshot/actions/runs/42580095042). Twice, the Windows run got GitHub 504s on all five retries while downloading upstream's `releases/download/v2.0.7/hookshot-setup.txt` (`release_parts.py previous`), and both times a re-run passed. That download is older than this PR. If it keeps happening, fetching the asset through the API (as `setup.c` does) would avoid it. A second Release run on the same commit made no release. - Afterwards I deleted the fork's test releases and tags (`v0.0.0-test.1`, `v0.0.0`, `v0.0.1-rc.1`, and the fork's `builder-parts` and `linux-parts`) and the `next` branch. ## What a maintainer needs to know or do - Before merging, make the v2.0.7 tag reachable from main. v2.0.7 (and v2.0.7-rc.1) point at 5656b04, a hotfix commit that is not on main. semantic-release only sees tags merged into the branch, so on main it still finds v2.0.6 as the last release, computes 2.0.7 from the `fix:` commits since, commits the `chore(release): 2.0.7` changelog entry to main, and then fails on `git tag v2.0.7` because the tag already exists. I reproduced this locally. Merge 5656b04 into main first: it conflicts in `scripts/linux/appimage.sh`, where main has its own header fix. After that, the first Release run after the merge cuts v2.0.8 from those `fix:` commits and builds its files. Every later merge with a `feat:` or `fix:` commit makes a release too, which is what docs/versioning.md already describes. - No secret is required while `main` and `next` have no branch protection, because `GITHUB_TOKEN` can push the release commit. If you protect them later, add a `RELEASE_TOKEN` secret (a fine-grained PAT with Contents: read and write on this repository, allowed to bypass the protection). The workflow already prefers it when it is set. - Under Settings > Actions > General > Workflow permissions, the default can stay read-only, since the jobs request what they need. If an organization policy caps `GITHUB_TOKEN` at read, the Release workflow cannot push or dispatch. - To keep uploading the Windows setup by hand, set the repository variable `CI_RELEASE_WINDOWS` to `false`. - To attach a release's files again, run Linux AppImage and Windows builder on its tag (Actions tab, Run workflow, pick the tag; or `gh workflow run linux-appimage.yml --ref <tag>`). - If a token you add (`RELEASE_TOKEN`) ever does start the tag workflows on the release tag in addition to the dispatch, each build runs twice. Both runs are in the same concurrency group, so they queue one after the other, and the second replaces the files with the same build. ## Which commits make a release semantic-release only counts Conventional Commit subjects. It ignores merge commits, but it does read the commits a merge brings in. On main since v2.0.0, there are 332 commits: 128 merges, and of the 204 others, 66 are conventional, 21 of them `feat`/`fix`/`perf`. Most of those 21 come from contributors' PRs. Most of the maintainers' own subjects are prose ("Pipeline state list: ...", "Lift: ..."), and those neither trigger a release nor appear in the notes or `changelog.txt`. So with this fix, releases would happen whenever a contributor's `fix:` or `feat:` lands, and a run of prose-only changes would never release. If that does not match how you want to cut versions, use conventional subjects (`fix(vk): ...`), or squash-merge with a conventional PR title. ## Alternative: release-please release-please would replace semantic-release with a release PR that it keeps up to date. A maintainer merges that PR to cut each version. The changelog in the PR can be edited before merging, every release goes through a human, and no npm install is needed in CI. It reads the same Conventional Commits, so prose subjects would still be left out unless the release PR is edited or `Release-As:` is used. Its tag and release are made with `GITHUB_TOKEN` too, so it has the same caveat: it needs a `RELEASE_TOKEN` PAT for the tag to start the build workflows, or the build workflows dispatched or called from the same workflow, as this PR does. I kept semantic-release because it is what `release.yml`, `.releaserc.json` and `docs/versioning.md` already set up, but I can switch this PR to release-please if you prefer.
hckrman101 commented 2026-11-09 08:21:34 +00:00 (Migrated from github.com)

Closing: this fixed the semantic-release workflow, but the release process is being replaced with release-please instead (a release PR with the changelog, files attached when it's merged). That comes as a new PR, along with a PR title check so titles are conventional.

Closing: this fixed the semantic-release workflow, but the release process is being replaced with release-please instead (a release PR with the changelog, files attached when it's merged). That comes as a new PR, along with a PR title check so titles are conventional.
Commenting is not possible because the repository is archived.
No description provided.