Copilot's review of the #44 rollout PRs (e.g. simp/pupmod-simp-aide#177) raised four points; all are valid and template-level, so they belong here rather than in the downstream repos. A fifth related instance turned up while verifying the first.
1. Asset-wipe step: forEach(async ...) deletions are not awaited (pre-existing, moved verbatim into resolve-release by #44)
modules/profile/files/_github/workflows/release_rpms.yml, "Wipe all previous assets" step:
existingAssets.data.forEach(async function(asset){
asset_id = asset.id
...
await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id })
})
forEach returns immediately; the step can report success before deletions complete (or after some fail silently).
asset_id is assigned without const/let.
Fix: for (const asset of existingAssets.data) { await ... } (or await Promise.all(...)), and declare the variable.
2. Same unawaited-promise pattern in the upload step (pre-existing; found while verifying #1)
The "Upload RPM file(s)" step builds conditionalClobber.then(...) promise chains inside a .map() and never awaits them — upload failures cannot fail the step, and completion relies on the node event loop draining before the runner tears down. Same fix: await the chains (for...of + await, or await Promise.all).
3. Wipe step only sees the first page of listReleaseAssets (pre-existing)
clean == 'yes' wipes at most one page (default 30 assets). Unlikely at 3-OS × (RPMs + GPG key) scale, but cheap to fix with github.paginate(github.rest.repos.listReleaseAssets, ...) while fixing #1.
4. Forge upload: find can match multiple tarballs (new in #44)
modules/profile/files/pupmod/_github/workflows/tag_deploy.yml, "Deploy to Puppet Forge" step:
file="$(find "$PWD/pkg" -name '*.tar.gz')"
With more than one *.tar.gz in pkg/, --form "file=@${file}" breaks (multi-line value) or uploads the wrong archive. checkout runs with clean: true and the build is fresh, so it shouldn't happen — but the guard is one line: fail with a clear error unless exactly one file matches. The same expansion feeds the artifact-upload and gh release upload steps, so a guard early in the job covers all three.
5. Validate build_container_oses before the matrix consumes it (new in #44)
An invalid JSON value fails the workflow at matrix-evaluation time with a cryptic error. Since resolve-release completes before the matrix job is scheduled, its existing "Validate inputs" step can parse the input (e.g. jq -e 'type == "array" and length > 0 and all(.[]; type == "string")') and emit a clear ::error:: naming the expected format ('["el8","el9","el10"]').
Rollout
All five are template fixes in release_rpms.yml / tag_deploy.yml; rolling them out is another scoped merge session (same shape as the 20260812 config). None are urgent: 1–3 only affect the manual clean: 'yes' path or >30-asset releases, and 4–5 are guards against conditions that don't occur in the tag-triggered path.
Copilot's review of the #44 rollout PRs (e.g. simp/pupmod-simp-aide#177) raised four points; all are valid and template-level, so they belong here rather than in the downstream repos. A fifth related instance turned up while verifying the first.
1. Asset-wipe step:
forEach(async ...)deletions are not awaited (pre-existing, moved verbatim intoresolve-releaseby #44)modules/profile/files/_github/workflows/release_rpms.yml, "Wipe all previous assets" step:forEachreturns immediately; the step can report success before deletions complete (or after some fail silently).asset_idis assigned withoutconst/let.Fix:
for (const asset of existingAssets.data) { await ... }(orawait Promise.all(...)), and declare the variable.2. Same unawaited-promise pattern in the upload step (pre-existing; found while verifying #1)
The "Upload RPM file(s)" step builds
conditionalClobber.then(...)promise chains inside a.map()and never awaits them — upload failures cannot fail the step, and completion relies on the node event loop draining before the runner tears down. Same fix: await the chains (for...of+await, orawait Promise.all).3. Wipe step only sees the first page of
listReleaseAssets(pre-existing)clean == 'yes'wipes at most one page (default 30 assets). Unlikely at 3-OS × (RPMs + GPG key) scale, but cheap to fix withgithub.paginate(github.rest.repos.listReleaseAssets, ...)while fixing #1.4. Forge upload:
findcan match multiple tarballs (new in #44)modules/profile/files/pupmod/_github/workflows/tag_deploy.yml, "Deploy to Puppet Forge" step:file="$(find "$PWD/pkg" -name '*.tar.gz')"With more than one
*.tar.gzinpkg/,--form "file=@${file}"breaks (multi-line value) or uploads the wrong archive.checkoutruns withclean: trueand the build is fresh, so it shouldn't happen — but the guard is one line: fail with a clear error unless exactly one file matches. The same expansion feeds the artifact-upload andgh release uploadsteps, so a guard early in the job covers all three.5. Validate
build_container_osesbefore the matrix consumes it (new in #44)An invalid JSON value fails the workflow at matrix-evaluation time with a cryptic error. Since
resolve-releasecompletes before the matrix job is scheduled, its existing "Validate inputs" step can parse the input (e.g.jq -e 'type == "array" and length > 0 and all(.[]; type == "string")') and emit a clear::error::naming the expected format ('["el8","el9","el10"]').Rollout
All five are template fixes in
release_rpms.yml/tag_deploy.yml; rolling them out is another scoped merge session (same shape as the 20260812 config). None are urgent: 1–3 only affect the manualclean: 'yes'path or >30-asset releases, and 4–5 are guards against conditions that don't occur in the tag-triggered path.