Problem
build-release.yml is not idempotent with respect to release assets. Re-running the workflow on a tag whose GitHub Release already exists (for example, to recover from a failed late step) rebuilds the distribution package and re-uploads it with gh release upload --clobber, replacing the already-published .tar.gz/.zip and their checksum files. Because gzip/zip embed mtimes and the npm run build output is not byte-reproducible, the re-uploaded assets get different checksums than the originally published ones — silently churning a public release.
Background
The tag-creation and release-creation steps are already idempotent: they skip when the tag exists (git ls-remote --tags --exit-code) and when the release exists (gh release view). The package build and upload steps have no such guard — they run on every non-dry_run invocation.
This surfaced while finishing the 8.1.0 release. The real run created the Release object and uploaded assets successfully, then failed at the final "Create CHANGELOG PRs" step (a separate bug, since fixed). Completing the release then meant either re-running the whole workflow (which would rebuild for ~9 min and clobber the published assets with new-checksum copies) or creating the missing CHANGELOG PR by hand. Re-running to finish a downstream step should not mutate already-published artifacts.
Files / Symbols
.github/workflows/build-release.yml
- "Build distribution package" step (~line 251) — always rebuilds via
task release:package:assemble.
- "Generate checksums" step (~line 258).
- "Create annotated tag and GitHub release" step (~line 264) — already idempotent; mirror this pattern.
- "Upload package and checksums to release" step (~line 312) — unconditional
gh release upload --clobber for !dry_run.
Approach
When dry_run is false and the release already exists with its expected assets, skip the rebuild and upload rather than clobbering. Suggested shape:
- Early in the non-dry-run path, probe the existing release's assets, e.g.
gh release view "$tag" --repo openemr/openemr --json assets, and decide whether the full expected set is already attached (two packages + the six checksum files + changelog.md).
- Gate "Build distribution package", "Generate checksums", and "Upload package and checksums to release" on assets being absent/incomplete — analogous to the existing skip-if-exists guards on tag/release creation.
- Leave
dry_run behavior unchanged (it should still build to validate, and never upload).
- Ensure later steps that legitimately need to run on a recovery re-run (e.g. "Create CHANGELOG PRs") still execute even when the build/upload were skipped.
Decide whether a partial/mismatched asset set should hard-fail (safer — avoids half-replacing) or trigger a deliberate re-upload; document the choice.
Definition of done
Out of scope
- The CHANGELOG PR target-branch fetch bug (already fixed).
- The package-assembly path bugs (already fixed).
- Making the package build itself byte-reproducible — a larger effort; this ticket only avoids needless re-upload.
Problem
build-release.ymlis not idempotent with respect to release assets. Re-running the workflow on a tag whose GitHub Release already exists (for example, to recover from a failed late step) rebuilds the distribution package and re-uploads it withgh release upload --clobber, replacing the already-published.tar.gz/.zipand their checksum files. Because gzip/zip embed mtimes and thenpm run buildoutput is not byte-reproducible, the re-uploaded assets get different checksums than the originally published ones — silently churning a public release.Background
The tag-creation and release-creation steps are already idempotent: they skip when the tag exists (
git ls-remote --tags --exit-code) and when the release exists (gh release view). The package build and upload steps have no such guard — they run on every non-dry_runinvocation.This surfaced while finishing the 8.1.0 release. The real run created the Release object and uploaded assets successfully, then failed at the final "Create CHANGELOG PRs" step (a separate bug, since fixed). Completing the release then meant either re-running the whole workflow (which would rebuild for ~9 min and clobber the published assets with new-checksum copies) or creating the missing CHANGELOG PR by hand. Re-running to finish a downstream step should not mutate already-published artifacts.
Files / Symbols
.github/workflows/build-release.ymltask release:package:assemble.gh release upload --clobberfor!dry_run.Approach
When
dry_runis false and the release already exists with its expected assets, skip the rebuild and upload rather than clobbering. Suggested shape:gh release view "$tag" --repo openemr/openemr --json assets, and decide whether the full expected set is already attached (two packages + the six checksum files +changelog.md).dry_runbehavior unchanged (it should still build to validate, and never upload).Decide whether a partial/mismatched asset set should hard-fail (safer — avoids half-replacing) or trigger a deliberate re-upload; document the choice.
Definition of done
build-release.yml(non-dry-run) on a tag whose release already has the full asset set does not rebuild or re-upload, and leaves the published asset bytes/checksums unchanged.dry_run=truestill builds for validation and uploads only the throwaway dry-run artifact.Out of scope