ci(release): show the error body, and retry the release POST
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m32s
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 1m32s
Run 75 built every artifact, pushed tag v0.1.1, then took a 500 from POST /releases one second later and exited 22. The tag was left orphaned with no release and no binaries, so the handoff fix in #22 reached no operator until the workflow was re-run by hand today. Re-running published the same four assets untouched, via the orphan-tag recovery the plan step already has. So the 500 was a race with the tag push -- Gitea had not finished processing the pushed tag when the POST arrived -- and not a bad request. Two separate gaps made that worse than it needed to be. `curl -sSf` prints no response body on an error status. All the log carried was "curl: (22) ... error: 500", so the cause had to be inferred from timestamps rather than read. Every call in this step now captures the body and prints it on failure, including the asset uploads. And nothing retried. The plan step can recover an orphan tag, but only on a run that reaches it, and a later push with no releasable commits stands down before it gets there -- which is why this one sat until someone looked. The POST now retries five times with a 5/10/15/20s backoff. 4xx is deliberately not retried: a bad token or a malformed body will not improve by being sent again, and retrying would turn a clear failure into a slow one. A give-up message names the orphan tag and says a re-run republishes. The asset uploads get the same treatment, because a release whose SHA256SUMS does not cover every binary it advertises is worse than no release -- that file is the trust anchor for an unsigned download. Verified by extracting the step's shell from the YAML and running the loop against a stubbed curl: first-try success, 500-then-success (the case that actually happened), two 500s then success, five 500s giving up, 403 and 404 aborting without retrying, and a 000 network failure being retried. bash -n clean and the YAML parses. Typed ci(...) rather than fix(...) on purpose: the plan step bumps on feat/fix, and this changes no binary, so a release here would be an empty one. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -395,17 +395,74 @@ jobs:
|
||||
# corrupt the Authorization header.
|
||||
CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')"
|
||||
|
||||
REL_ID="$(curl -sSf -X POST "${API}/releases" \
|
||||
PAYLOAD="$(jq -n --arg tag "$TAG" --arg body "$BODY" \
|
||||
'{tag_name:$tag, name:$tag, body:$body, draft:false, prerelease:false}')"
|
||||
|
||||
# This POST is the step that orphaned tag v0.1.1 (run 75): it landed one
|
||||
# second after the tag push and Gitea answered 500, having not finished
|
||||
# processing the pushed tag. Re-running the workflow published the same
|
||||
# four assets untouched, so the failure was a race, not a bad request.
|
||||
#
|
||||
# Two things went wrong there, and both are fixed here.
|
||||
#
|
||||
# 1. `curl -sSf` prints NO response body on an error status, so all the
|
||||
# log carried was "curl: (22) ... error: 500" and the cause had to be
|
||||
# inferred from timestamps. Capture the body and print it.
|
||||
# 2. Nothing retried, so a transient 5xx became a permanent orphan tag.
|
||||
# The plan step CAN recover one, but only on a run that reaches it --
|
||||
# and a later push with no releasable commits stands down before it
|
||||
# gets there, so in practice the tag sits until a human notices.
|
||||
#
|
||||
# 4xx is deliberately NOT retried: a bad token or a malformed body does
|
||||
# not improve by being sent again, and retrying only turns a clear
|
||||
# failure into a slow one.
|
||||
REL_ID=""
|
||||
for attempt in 1 2 3 4 5; do
|
||||
HTTP="$(curl -s -o /tmp/rel.json -w '%{http_code}' -X POST "${API}/releases" \
|
||||
-H "Authorization: token ${CI_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg tag "$TAG" --arg body "$BODY" \
|
||||
'{tag_name:$tag, name:$tag, body:$body, draft:false, prerelease:false}')" \
|
||||
| jq -r '.id')"
|
||||
-d "${PAYLOAD}" || echo 000)"
|
||||
|
||||
if [ "$HTTP" = "201" ] || [ "$HTTP" = "200" ]; then
|
||||
REL_ID="$(jq -r '.id' /tmp/rel.json)"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "::warning::POST /releases attempt ${attempt} returned HTTP ${HTTP}"
|
||||
echo "--- response body ---"
|
||||
cat /tmp/rel.json || true
|
||||
echo
|
||||
echo "---------------------"
|
||||
|
||||
case "$HTTP" in
|
||||
4*) echo "::error::HTTP ${HTTP} is a client error - not retrying."; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [ "$attempt" = 5 ]; then
|
||||
echo "::error::POST /releases still failing after 5 attempts. Tag ${TAG} is pushed but has no release."
|
||||
echo "::error::Re-run this workflow - the plan step detects the orphan tag and republishes it."
|
||||
exit 1
|
||||
fi
|
||||
sleep $(( attempt * 5 ))
|
||||
done
|
||||
|
||||
if [ -z "$REL_ID" ] || [ "$REL_ID" = "null" ]; then
|
||||
echo "::error::Release created but no id came back; refusing to upload assets blind."
|
||||
exit 1
|
||||
fi
|
||||
echo "Created release ${TAG} (id=${REL_ID})"
|
||||
|
||||
for f in "${BIN}-linux-x86_64" "${BIN}-linux-aarch64" "${BIN}-windows-x86_64.exe" SHA256SUMS; do
|
||||
curl -sSf -X POST "${API}/releases/${REL_ID}/assets?name=${f}" \
|
||||
# Same treatment. An upload that fails quietly leaves a release whose
|
||||
# SHA256SUMS does not cover every binary it advertises, which is worse
|
||||
# than no release at all -- that file IS the trust anchor.
|
||||
HTTP="$(curl -s -o /tmp/asset.json -w '%{http_code}' -X POST "${API}/releases/${REL_ID}/assets?name=${f}" \
|
||||
-H "Authorization: token ${CI_TOKEN}" \
|
||||
-F "attachment=@dist/${f}" >/dev/null
|
||||
-F "attachment=@dist/${f}" || echo 000)"
|
||||
if [ "$HTTP" != "201" ] && [ "$HTTP" != "200" ]; then
|
||||
echo "::error::uploading ${f} returned HTTP ${HTTP}"
|
||||
cat /tmp/asset.json || true
|
||||
exit 1
|
||||
fi
|
||||
echo " uploaded ${f}"
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user