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=""

Reply via email to