@smbcloud / cli / commits / 7582f45

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