This is an automated email from the ASF dual-hosted git repository.
acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 9c2f60d3ed1 .github: Try a failed GitHub API call again.
9c2f60d3ed1 is described below
commit 9c2f60d3ed1a2124e077fc134ceee259fa7fcd07
Author: Marco Casaroli <[email protected]>
AuthorDate: Tue Nov 3 17:21:55 2026 +0100
.github: Try a failed GitHub API call again.
The depends-on steps of build.yml, bluetooth.yml and
membrowse-report.yml read the PR description and the state of each
dependency with "gh api". The GitHub API fails sometimes for a short
time: a network error, a 5xx answer, a rate limit, or an error page
instead of JSON ("invalid character 'S' looking for beginning of
value"). One such failure ends a membrowse analyze job ("Could not read
the PR description."), and in the other steps it makes the step use the
old PR description or apply a dependency that is already merged.
Add .github/scripts/gh_api_retry.sh with gh_api_retry(), which runs
"gh api" again after 5, 10 and 20 seconds, but not after HTTP 404 or
422, and use it for the seven calls.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <[email protected]>
---
.github/scripts/gh_api_retry.sh | 58 ++++++++++++++++++++++++++++++++++
.github/workflows/bluetooth.yml | 6 ++--
.github/workflows/build.yml | 8 +++--
.github/workflows/membrowse-report.yml | 10 ++++--
4 files changed, 75 insertions(+), 7 deletions(-)
diff --git a/.github/scripts/gh_api_retry.sh b/.github/scripts/gh_api_retry.sh
new file mode 100644
index 00000000000..85cabd223d7
--- /dev/null
+++ b/.github/scripts/gh_api_retry.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# .github/scripts/gh_api_retry.sh
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Source this file, then call gh_api_retry with the arguments of "gh api".
+# The GitHub API sometimes fails for a short time: a network error, a 5xx
+# answer, a rate limit, or an error page instead of JSON. gh_api_retry
+# tries again after 5, 10 and 20 seconds. It does not try again when the
+# answer is HTTP 404 or 422, because the next try gives the same answer.
+# It prints the output of the successful try and returns 0, or it prints
+# the errors of the last try to stderr and returns 1.
+
+gh_api_retry()
+{
+ local delay=5
+ local err
+ local out
+ local try
+
+ err=$(mktemp)
+ for try in 1 2 3 4; do
+ if out=$(gh api "$@" 2> "$err"); then
+ rm -f "$err"
+ printf '%s\n' "$out"
+ return 0
+ fi
+
+ if grep -q 'HTTP 404\|HTTP 422' "$err" || [ "$try" -eq 4 ]; then
+ break
+ fi
+
+ echo "gh api $1 failed (try $try of 4), trying again in ${delay} s:" \
+ "$(head -c 200 "$err")" >&2
+ sleep "$delay"
+ delay=$((delay * 2))
+ done
+
+ cat "$err" >&2
+ rm -f "$err"
+ return 1
+}
diff --git a/.github/workflows/bluetooth.yml b/.github/workflows/bluetooth.yml
index a2bbd1ab603..28b15df9059 100644
--- a/.github/workflows/bluetooth.yml
+++ b/.github/workflows/bluetooth.yml
@@ -91,8 +91,10 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
shell: bash
run: |
+ . nuttx/.github/scripts/gh_api_retry.sh
+
# Reuse the parser used by the main build workflow.
- if body=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
+ if body=$(gh_api_retry "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" \
--jq '.body // ""'); then
export PR_BODY="$body"
else
@@ -118,7 +120,7 @@ jobs:
*) exit 1 ;;
esac
query='[.state, .merged, (.merge_commit_sha // "-")] | @tsv'
- if info=$(gh api "repos/$repo/pulls/$number" --jq "$query"); then
+ if info=$(gh_api_retry "repos/$repo/pulls/$number" --jq "$query");
then
read -r status merged merge_sha <<< "$info"
else
echo "::warning::Could not read the state of $dependency;" \
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e9e8f501cd2..9601b778e17 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -63,6 +63,8 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REPORT_PATH: depends-on-report/result.json
run: |
+ . .github/scripts/gh_api_retry.sh
+
OS_REF=""
APPS_REF=""
@@ -70,7 +72,7 @@ jobs:
# created; re-read it so a manual re-run picks up an edited
# Depends-On line. Keep the payload copy if the API call fails.
if [ -n "${PR_NUMBER:-}" ]; then
- if LIVE_BODY="$(gh api
"repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"; then
+ if LIVE_BODY="$(gh_api_retry
"repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"; then
PR_BODY="$LIVE_BODY"
else
echo "::warning::Could not re-read the PR description; using the
copy from the event payload."
@@ -153,6 +155,8 @@ jobs:
run: |
set -uo pipefail
+ . .github/scripts/gh_api_retry.sh
+
git config --global user.email "[email protected]"
git config --global user.name "github-actions"
@@ -217,7 +221,7 @@ jobs:
# Skip a dependency whose merge commit is already in the checkout.
# Its commits are in the target branch and cannot be applied again.
# A dependency that is closed without merge is an error.
- if ! DEP_INFO=$(gh api "repos/${DEP_REPO}/pulls/${DEP_PR_NUM}" \
+ if ! DEP_INFO=$(gh_api_retry
"repos/${DEP_REPO}/pulls/${DEP_PR_NUM}" \
--jq 'if .merged then "merged \(.merge_commit_sha)" else
.state end'); then
echo "::warning::Could not read the state of ${DEP}; applying
it."
DEP_INFO=""
diff --git a/.github/workflows/membrowse-report.yml
b/.github/workflows/membrowse-report.yml
index e7a9522fe54..06b1d3e0503 100644
--- a/.github/workflows/membrowse-report.yml
+++ b/.github/workflows/membrowse-report.yml
@@ -45,6 +45,8 @@ jobs:
HEAD_SHA: ${{ github.event_name == 'pull_request' &&
github.event.pull_request.head.sha || github.sha }}
GH_TOKEN: ${{ github.token }}
run: |
+ . .github/scripts/gh_api_retry.sh
+
# On the very first push to a branch, github.event.before is
all-zeros.
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" =
"0000000000000000000000000000000000000000" ]; then
echo "source=true" >> "$GITHUB_OUTPUT"
@@ -59,7 +61,7 @@ jobs:
#
# GitHub's compare API uses 3-dot semantics, so
.merge_base_commit.sha
# is the fork point. gh is pre-installed on the runner.
- MERGE_BASE=$(gh api \
+ MERGE_BASE=$(gh_api_retry \
"repos/$GITHUB_REPOSITORY/compare/$BASE_SHA...$HEAD_SHA" \
--jq '.merge_base_commit.sha' 2>/dev/null || true)
if [ -z "$MERGE_BASE" ]; then
@@ -204,11 +206,13 @@ jobs:
run: |
set -uo pipefail
+ . sources/nuttx/.github/scripts/gh_api_retry.sh
+
# Re-read the current body so a re-run picks up an edited Depends-On
# line. Do not fall back to the event payload: unlike build.yml,
which
# resolves this once for every target, each matrix leg resolves
# independently and could otherwise use a different declaration.
- if ! PR_BODY="$(gh api
"repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"; then
+ if ! PR_BODY="$(gh_api_retry
"repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.body // ""')"; then
echo "::error::Could not read the PR description."
exit 1
fi
@@ -319,7 +323,7 @@ jobs:
# Skip a merged dependency and reject a closed one, as build.yml
# does.
- if ! DEP_INFO=$(gh api "repos/${DEP_REPO}/pulls/${DEP_NUM}" \
+ if ! DEP_INFO=$(gh_api_retry "repos/${DEP_REPO}/pulls/${DEP_NUM}" \
--jq 'if .merged then "merged \(.merge_commit_sha)" else
.state end'); then
echo "::warning::Could not read the state of ${DEP}; applying
it."
DEP_INFO=""