From cbbf3f6baa97a0447d9bf80dd40bfd0e68e32e0e Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 14:08:39 -0400 Subject: [PATCH] fix(release): harden publish curl against Cloudflare HTTP/2 stream errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dispatched v0.1.1 build succeeded a third time (2m06s, valid ARM ELF) and then died at the first API call inside the publish step with curl exit 92 (HTTP/2 stream error) — job containers reach Forgejo through Cloudflare, where that is intermittent. - all API calls go through api_curl: --http1.1 --retry 5 --retry-all-errors --retry-delay 3 --connect-timeout 20 --max-time 300 - HTTP/1.1 is the documented workaround for this instance's HTTP/2 resets Job containers cannot reach http://forgejo:3000 (isolated GITEA-ACTIONS network, name unresolvable, container IP unroutable — measured), so the public hostname plus retries is the available fix. Verified locally: create + idempotent re-run against a throwaway tag, both clean; test release and tag deleted. --- scripts/publish-release.sh | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/scripts/publish-release.sh b/scripts/publish-release.sh index 23d8cdc..a7732e3 100755 --- a/scripts/publish-release.sh +++ b/scripts/publish-release.sh @@ -12,6 +12,15 @@ # same-named assets instead of failing with 409. set -euo pipefail +# Every API call goes through here. Publishing runs from CI containers that +# reach Forgejo through Cloudflare, where a bare HTTP/2 request intermittently +# dies with curl exit 92 (stream error) — retries plus forcing HTTP/1.1 make +# that a non-event. +api_curl() { + curl -sS --http1.1 --retry 5 --retry-all-errors --retry-delay 3 \ + --connect-timeout 20 --max-time 300 "$@" +} + tag="${1:?usage: publish-release.sh ...}" name="${2:?missing release name}" body_file="${3:?missing body file}" @@ -41,18 +50,18 @@ if sys.argv[3]: print(json.dumps(payload)) PY -code=$(curl -s -o /tmp/release-rel.json -w '%{http_code}' \ +code=$(api_curl -o /tmp/release-rel.json -w '%{http_code}' \ -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/tags/$tag") if [ "$code" = "404" ]; then echo "publish: creating release $tag" - curl -sf -X POST "$api/releases" \ + api_curl -sf -X POST "$api/releases" \ -H "Authorization: Bearer $FORGEJO_TOKEN" \ -H 'Content-Type: application/json' \ --data @/tmp/release-body.json -o /tmp/release-rel.json elif [ "$code" = "200" ]; then echo "publish: release $tag exists, updating" rid=$(jqp "d['id']" < /tmp/release-rel.json) - curl -sf -X PATCH "$api/releases/$rid" \ + api_curl -sf -X PATCH "$api/releases/$rid" \ -H "Authorization: Bearer $FORGEJO_TOKEN" \ -H 'Content-Type: application/json' \ --data @/tmp/release-body.json -o /tmp/release-rel.json @@ -67,7 +76,7 @@ echo "publish: release id $rid ($tag)" for f in "$@"; do [ -f "$f" ] || { echo "publish: missing asset $f" >&2; exit 1; } asset=$(basename "$f") - curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ + api_curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ | python3 -c " import json,sys for a in json.load(sys.stdin): @@ -75,11 +84,11 @@ for a in json.load(sys.stdin): " | while read -r aid aname; do if [ "$aname" = "$asset" ]; then echo "publish: deleting stale asset $aname ($aid)" - curl -sf -X DELETE -H "Authorization: Bearer $FORGEJO_TOKEN" \ + api_curl -sf -X DELETE -H "Authorization: Bearer $FORGEJO_TOKEN" \ "$api/releases/$rid/assets/$aid" > /dev/null fi done - curl -sf -X POST "$api/releases/$rid/assets?name=$asset" \ + api_curl -sf -X POST "$api/releases/$rid/assets?name=$asset" \ -H "Authorization: Bearer $FORGEJO_TOKEN" \ -F "attachment=@$f" -o /tmp/release-asset.json python3 -c " @@ -87,7 +96,7 @@ import json;d=json.load(open('/tmp/release-asset.json')) print('publish: uploaded', d['name'], d['size'], 'bytes')" done -curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ +api_curl -s -H "Authorization: Bearer $FORGEJO_TOKEN" "$api/releases/$rid/assets" \ | python3 -c " import json,sys for a in json.load(sys.stdin):