From e12403b34880dd1a4fce1b0f624b6086e853064b Mon Sep 17 00:00:00 2001 From: rhiroe Date: Wed, 9 Sep 2026 03:12:06 +0000 Subject: [PATCH] improve(github-cli): retry gh extension install before falling back to git clone gh extension install failures fell through silently to the git-clone fallback. Retry it up to 3 times with backoff before falling back, since failures are most often transient (network/API rate limiting) rather than the extension being fundamentally unusable, and log each failed attempt's exit code so provisioning logs show why the fallback was hit. Extensions that require a build step (e.g. github/gh-stack) are not usable after a plain git clone. Improving the fallback itself (e.g. downloading a prebuilt release binary) would add significant logic and ongoing maintenance cost, so that path is not pursued here; instead, after cloning, check for the expected gh- executable and exit 1 if it's missing, so a build-requiring extension fails provisioning loudly instead of leaving a broken extension in place. --- src/github-cli/scripts/install-extensions.sh | 35 ++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/github-cli/scripts/install-extensions.sh b/src/github-cli/scripts/install-extensions.sh index 05285aa7b..be861283a 100644 --- a/src/github-cli/scripts/install-extensions.sh +++ b/src/github-cli/scripts/install-extensions.sh @@ -20,19 +20,42 @@ install_extension() { local extension="$1" local extensions_root local repo_name + local install_status + local attempt + local max_attempts=3 extensions_root="${XDG_DATA_HOME:-"${HOME}/.local/share"}/gh/extensions" repo_name="${extension##*/}" mkdir -p "${extensions_root}" - if [ ! -d "${extensions_root}/${repo_name}" ]; then - if ! gh extension install "${extension}"; then - git \ - -c credential.helper= \ - -c credential.helper='!gh auth git-credential' \ - clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}" + if [ -d "${extensions_root}/${repo_name}" ]; then + return + fi + + attempt=1 + while [ "${attempt}" -le "${max_attempts}" ]; do + if gh extension install "${extension}"; then + return + fi + install_status=$? + echo "Warning: 'gh extension install ${extension}' failed (exit code ${install_status}, attempt ${attempt}/${max_attempts})." >&2 + attempt=$((attempt + 1)) + if [ "${attempt}" -le "${max_attempts}" ]; then + sleep $((attempt * 2)) fi + done + + git \ + -c credential.helper= \ + -c credential.helper='!gh auth git-credential' \ + clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}" + + if [ ! -x "${extensions_root}/${repo_name}/gh-${repo_name}" ]; then + echo "Error: '${extension}' requires a build step; 'git clone' fallback won't work." >&2 + rm -rf "${extensions_root}/${repo_name}" + exit 1 fi + echo "Warning: cloned ${extension} instead of installing via 'gh extension install'." >&2 } ensure_gh_extension_list_wrapper() {