ci(gem-release): fail loudly on push errors; add credential-test toggle
The push step swallowed every gem push failure as "may already exist" and exited 0, so a 401 from a missing RUBYGEMS_API_KEY looked like a successful, no-op release (v0.4.0–v0.4.3 reported green but published nothing). Now: - credentials step fails fast if RUBYGEMS_API_KEY is empty/unset - push step only skips a genuine "Repushing not allowed" response and fails the job on any other error (401, validation, 5xx) - new force_push_existing dispatch input runs the push against an already-published version to verify credentials without creating a new release (RubyGems rejects the re-push) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
keypair34 committed
Jun 17, 2026 at 16:09 UTC
7582f45278a5fe2463079887c762262c30950847
1 file changed
+45
-8
.github/workflows/release-sdk-gem.yml
+45
-8
@@ -13,6 +13,11 @@ on:
13
tag:
14
description: "Release tag (e.g. v0.3.35)"
15
required: true
16
+ force_push_existing:
17
+ description: "Attempt the push even if this version is already on RubyGems. Credential test only — RubyGems rejects re-push, so no new release is created."
18
+ type: boolean
19
+ default: false
20
+ required: false
21
22
permissions:
23
contents: read
@@ -206,7 +211,10 @@ jobs:
211
id: rubygems-check
212
shell: bash
213
run: |
209
- if curl -fsS \
214
+ if [[ "${{ github.event.inputs.force_push_existing }}" == "true" ]]; then
215
+ echo "force_push_existing is set — running the push even if the version exists (credential test)."
216
+ echo "exists=false" >> "$GITHUB_OUTPUT"
217
+ elif curl -fsS \
218
"https://rubygems.org/api/v2/rubygems/smbcloud-auth/versions/${RELEASE_VERSION}.json" \
219
>/dev/null 2>&1; then
220
echo "smbcloud-auth ${RELEASE_VERSION} already exists on RubyGems, skipping publish"
@@ -232,20 +240,49 @@ jobs:
240
- name: Configure RubyGems credentials
241
if: steps.rubygems-check.outputs.exists != 'true'
242
shell: bash
243
+ env:
244
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
245
run: |
246
+ # Fail fast and loudly if the secret is missing or empty. Without this
247
+ # guard, gem push gets blank credentials, returns 401, and the push
248
+ # step used to disguise that as "already exists" — silently shipping
249
+ # nothing while the run stayed green.
250
+ if [ -z "${RUBYGEMS_API_KEY}" ]; then
251
+ echo "::error::RUBYGEMS_API_KEY secret is empty or unset. Add a RubyGems API key with push scope as the RUBYGEMS_API_KEY repository secret."
252
+ exit 1
253
+ fi
254
mkdir -p "${HOME}/.gem"
237
- echo ":rubygems_api_key: ${{ secrets.RUBYGEMS_API_KEY }}" > "${HOME}/.gem/credentials"
255
+ printf ':rubygems_api_key: %s\n' "${RUBYGEMS_API_KEY}" > "${HOME}/.gem/credentials"
256
chmod 0600 "${HOME}/.gem/credentials"
257
240
- # Push each .gem file individually. Platform-specific gems that are already
241
- # published (e.g. from a re-run) emit a warning and continue rather than
242
- # failing the whole job.
258
+ # Push each .gem file individually. A version that is genuinely already
259
+ # published (e.g. a partial re-run) is skipped; any other failure —
260
+ # bad credentials, validation errors, server errors — fails the job.
261
- name: Push gems to RubyGems
262
if: steps.rubygems-check.outputs.exists != 'true'
263
shell: bash
264
run: |
265
+ set -uo pipefail
266
+ push_failed=0
267
for gem_file in gems/*.gem; do
248
- echo "Pushing $(basename "${gem_file}")…"
249
- gem push "${gem_file}" \
250
- || echo "::warning::$(basename "${gem_file}") may already exist for this version — skipping."
268
+ name="$(basename "${gem_file}")"
269
+ echo "Pushing ${name}…"
270
+ if push_output="$(gem push "${gem_file}" 2>&1)"; then
271
+ echo "${push_output}"
272
+ echo "Pushed ${name}."
273
+ continue
274
+ fi
275
+ echo "${push_output}"
276
+ # The only acceptable failure is the version already existing on
277
+ # RubyGems; repushing is rejected with this specific message.
278
+ if echo "${push_output}" | grep -qiE "Repushing of gem versions is not allowed|has already been pushed"; then
279
+ echo "::notice::${name} is already published for this version — skipping."
280
+ continue
281
+ fi
282
+ echo "::error::Failed to push ${name}."
283
+ push_failed=1
284
done
285
+ if [ "${push_failed}" -ne 0 ]; then
286
+ echo "::error::One or more gems failed to push (see logs above). The release did not fully publish."
287
+ exit 1
288
+ fi