From 6dae4fe42856127cbb1a7fa5e1dfa949b6edaa28 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 14 Jun 2026 19:29:49 +0200 Subject: [PATCH] ci(nightly): surface a clear error when the Gitea API rejects the audit token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The npm-audit job filed its tracking issue via `curl -sf`, which collapses every HTTP >=400 into a bare "exit 22". When the NIGHTLY_AUDIT_TOKEN secret is missing, expired, or under-scoped, the step failed with an opaque `exitcode '22'` and no hint at the cause (run #6707). Route all five API calls through an `api()` helper that reads the HTTP status and, on >=400, emits an actionable `::error::` naming the status and the token secret before failing — without ever echoing the token value. Extend the in-workflow self-test (mocked curl) to cover both the success and HTTP-error paths. Closes #839 Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/nightly.yml | 70 +++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml index b8b59cdc..d28989cc 100644 --- a/.gitea/workflows/nightly.yml +++ b/.gitea/workflows/nightly.yml @@ -192,17 +192,52 @@ jobs: REPO="${{ github.repository }}" RUN_URL="${GITEA_URL}/${REPO}/actions/runs/${{ github.run_id }}" + # --- Gitea API helper --- + # api METHOD URL [extra curl args...] — authenticated Gitea API call. + # `curl -sf` collapses every HTTP >=400 into a bare "exit 22", which + # surfaces as an opaque step failure (issue #839). Instead we read the + # status code and, on a >=400 response, print an actionable ::error:: + # to stderr (so a calling command substitution does not swallow it) and + # return 1 — `set -e` then still fails the step. The token is never + # echoed (no set -x; never placed in the message). + api() { + local method="$1" url="$2"; shift 2 + local resp http + resp=$(curl -s -w '\n%{http_code}' -X "$method" \ + -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" "$@" -- "$url") + http=${resp##*$'\n'} + printf '%s' "${resp%$'\n'*}" + case "$http" in + 2*|3*) return 0 ;; + 401|403) + echo "::error::Gitea returned HTTP $http for $method ${url%%\?*} — the NIGHTLY_AUDIT_TOKEN secret is missing, expired, or lacks issue read+write scope; recreate the renovate_bot PAT and update the secret." >&2 + return 1 ;; + *) + echo "::error::Gitea returned HTTP ${http:-(none)} for $method ${url%%\?*}." >&2 + return 1 ;; + esac + } + # --- Self-test (mirrors ci.yml §Assert pattern) --- - # Tests the exact jq test() call used in the dedupe step, before any - # API call, so a broken matcher fails loudly early rather than silently - # opening duplicate issues. Proves the regex only — create-vs-update - # decision is exercised by the workflow_dispatch AC. + # Runs before any real API call so broken logic fails loudly early: + # (a) the jq title matcher used by the dedupe step — proves the regex + # only; the create-vs-update decision is exercised by the + # workflow_dispatch AC; + # (b) the api helper's HTTP-status handling, driven by a mocked curl so + # it needs no network — proves a 2xx returns the body and a >=400 + # fails with an ::error:: instead of an opaque exit 22. echo "{\"title\": \"${MARKER}\"}" \ | jq -e --arg m "$MARKER" '.title | test($m; "i")' > /dev/null \ || { echo "FAIL: self-test — jq test() missed tracking issue title"; exit 1; } echo '{"title": "fix(deps): update dependency esbuild (CVE-2025-12345)"}' \ | jq -e --arg m "$MARKER" '.title | test($m; "i") | not' > /dev/null \ || { echo "FAIL: self-test — jq test() incorrectly matched unrelated title"; exit 1; } + ( curl() { printf 'OK\n200'; }; [ "$(api GET selftest)" = "OK" ] ) \ + || { echo "FAIL: self-test — api helper dropped body on HTTP 200"; exit 1; } + ( curl() { printf 'nope\n401'; } + if api GET selftest >/dev/null 2>/tmp/api_selftest_err; then exit 1; fi + grep -q '::error::' /tmp/api_selftest_err ) \ + || { echo "FAIL: self-test — api helper did not emit ::error:: on HTTP 401"; exit 1; } echo "Self-test passed." # --- Run audit --- @@ -237,8 +272,7 @@ jobs: # Renovate vuln PRs also carry the "security" label, so >1 open # "security" issue WILL occur. Title-match (not just label) ensures # we deduplicate only our own tracking issue. - OPEN_ISSUES=$(curl -sf \ - -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \ + OPEN_ISSUES=$(api GET \ "${GITEA_URL}/api/v1/repos/${REPO}/issues?state=open&type=issues&labels=security&limit=50") MATCHED=$(echo "$OPEN_ISSUES" | jq \ @@ -255,11 +289,10 @@ jobs: --arg run_url "$RUN_URL" \ '$existing + "\n\n---\n\nUpdated by run: " + $run_url') PAYLOAD=$(jq -n --arg body "$NEW_BODY" '{"body": $body}') - curl -sf -X PATCH \ - -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \ + api PATCH \ + "${GITEA_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" \ -H "Content-Type: application/json" \ - -d "$PAYLOAD" \ - "${GITEA_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" > /dev/null + -d "$PAYLOAD" > /dev/null echo "Updated tracking issue #${ISSUE_NUMBER}" else # Closed prior issue that recurs → new issue (not reopened). @@ -268,24 +301,21 @@ jobs: --arg title "$MARKER" \ --arg body "$ISSUE_BODY" \ '{"title": $title, "body": $body}') - CREATED=$(curl -sf -X POST \ - -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \ + CREATED=$(api POST \ + "${GITEA_URL}/api/v1/repos/${REPO}/issues" \ -H "Content-Type: application/json" \ - -d "$PAYLOAD" \ - "${GITEA_URL}/api/v1/repos/${REPO}/issues") + -d "$PAYLOAD") NEW_NUMBER=$(echo "$CREATED" | jq -r '.number') echo "Opened new tracking issue #${NEW_NUMBER}" # Labels are ignored on issue create in Gitea — add in a follow-up call. - LABEL_IDS=$(curl -sf \ - -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \ + LABEL_IDS=$(api GET \ "${GITEA_URL}/api/v1/repos/${REPO}/labels?limit=50" \ | jq '[.[] | select(.name == "security" or .name == "devops" or .name == "P1-high") | .id]') - curl -sf -X POST \ - -H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \ + api POST \ + "${GITEA_URL}/api/v1/repos/${REPO}/issues/${NEW_NUMBER}/labels" \ -H "Content-Type: application/json" \ - -d "{\"labels\": $LABEL_IDS}" \ - "${GITEA_URL}/api/v1/repos/${REPO}/issues/${NEW_NUMBER}/labels" > /dev/null + -d "{\"labels\": $LABEL_IDS}" > /dev/null fi exit "$AUDIT_EXIT"