Skip to content

fix(strix): retry target-repository visibility lookup on transient failure - #1214

Open
seonghobae wants to merge 4 commits into
mainfrom
fix/strix-visibility-lookup-rate-limit-retry
Open

fix(strix): retry target-repository visibility lookup on transient failure#1214
seonghobae wants to merge 4 commits into
mainfrom
fix/strix-visibility-lookup-rate-limit-retry

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Root cause

The Strix required check's "Resolve target repository visibility" step ran a
single unretried gh api repos/<target> call. Observed failure on
fast-mlsirm#1192 (Strix Security Scan run 32526888686, job
96910702255):

gh: API rate limit exceeded for installation ID 141441800. ...
##[error]Process completed with exit code 1.

That is the shared GitHub App installation token used by CI across the
organization, not a per-repository credential. With roughly 190 open pull
requests across just fast-mlsirm and TEPP today, each running its own
hourly review-repair scheduler plus push-triggered Strix/OpenCode/Noema
dispatches, this installation token's hourly quota is a real, recurring
point of contention. When it is briefly exhausted, this single-shot lookup
fails the entire required Strix check immediately — with no vulnerability
report and no code defect involved — blocking otherwise fully reviewed,
fully green pull requests fleet-wide.

Fix

Retry the visibility lookup up to 6 times with linear backoff (5s × attempt)
before failing, mirroring the existing PR-head-fetch retry loop later in
this same workflow (for pr_head_fetch_attempt in 1 2 3 4 5 6 ... sleep 10).
Still fails closed with ::error:: if visibility genuinely cannot be
resolved after all retries. No change to Strix's scan semantics, model
routing, provider selection, or bypass posture — this only hardens a
transport-layer lookup against a transient 403.

Scope boundary

Workflow-only change to one step. Does not touch scan execution, findings
handling, merge gating, or credentials.

Verification

  • actionlint .github/workflows/strix.yml (embedded shellcheck): clean.
  • scripts/ci/test_strix_quick_gate.sh: test_strix_quick_gate: PASS
    (exit 0), unchanged pass/fail set — the retry loop does not alter any of
    the file-content assertions this suite checks.

Open in Devin Review

…ilure

The "Resolve target repository visibility" step ran a single unretried
`gh api repos/<target>` call. When the shared GitHub App installation
token hits its hourly rate limit (observed on fast-mlsirm#1192, exit
`API rate limit exceeded for installation ID 141441800`), that single
call fails and the entire required Strix check fails closed with no
security finding, immediately blocking review even though every other
check and thread on the PR is clean. With ~190 open PRs across just
fast-mlsirm and TEPP each running hourly review schedulers, this
installation-token contention is a recurring, non-code-defect cause of
stalled required checks org-wide.

Retry the lookup up to 6 times with linear backoff (5s x attempt)
before failing, mirroring the existing PR-head-fetch retry loop later
in this same workflow. Still fails closed if visibility genuinely
cannot be resolved after retries; does not change Strix's scan
semantics, model routing, or bypass posture.

Verified: actionlint (incl. embedded shellcheck) clean;
scripts/ci/test_strix_quick_gate.sh passes unchanged.
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e8bf66ef-ab29-46d5-8a78-07b39f7f779f

📥 Commits

Reviewing files that changed from the base of the PR and between 5a8b837 and dcf45dc.

📒 Files selected for processing (4)
  • .github/workflows/strix.yml
  • CHANGELOG.md
  • scripts/ci/strix_quick_gate.sh
  • tests/test_strix_caido_bootstrap_timing_retry.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +273 to +282
for target_visibility_attempt in 1 2 3 4 5 6; do
if is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"; then
break
fi
is_private=""
if [ "$target_visibility_attempt" -lt 6 ]; then
echo "Repository visibility lookup failed (attempt ${target_visibility_attempt}/6), possibly a transient GitHub API rate limit; retrying after backoff." >&2
sleep "$(( target_visibility_attempt * 5 ))"
fi
done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Retry loop delays permanent failures too

The loop retries any gh api failure six times, not only transient rate limits. A permanent failure (404, denied auth) now waits the full 75s backoff before failing closed instead of failing immediately.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

The sandbox container strix-agent launches runs a chown -R before
starting caido-cli, and enforces a fixed 10-attempt loginAsGuest budget.
A slow CI runner can exceed that budget before the local intercepting
proxy is reachable, even though the penetration test itself never
started and no vulnerability evidence was produced or lost:

  Error during penetration test: loginAsGuest failed after 10 attempts:
  curl exit 7: curl: (7) Failed to connect to 127.0.0.1 port 48080
  after 0 ms: Could not connect to server

Confirmed via `gh api search/issues` on usestrix/strix as a live,
open upstream bug (not our misconfiguration): #1037 "Strix 1.5.2 fails
in GitHub Actions: Caido bootstrap (loginAsGuest) cannot reach
127.0.0.1:48080", #1036 "loginAsGuest gives up after ~68s: fixed
10-attempt retry too short for slow sandbox boot", #1056 "fix: bound
Caido readiness probe by wall-clock deadline, not fixed attempts"
(all open). strix-agent==1.5.3 is the current PyPI release; our
strix.yml invocation skips no setup step and the sandbox image was
already pulled and ready before the failure.

strix_quick_gate.sh already retries the same model up to
STRIX_TRANSIENT_RETRY_PER_MODEL times (2, set in strix.yml) for
rate-limit / LLM-connection / service-unavailable / midstream-fallback
errors, but had no classifier for this signature, so it fell straight
through to a hard `gate exit 1` on first occurrence. Add
is_caido_bootstrap_timing_error() and wire it into
is_transient_same_model_retry_error (retry — the Docker image is
already cached, so a retry is cheap and usually clears a one-off boot
race) and has_detected_infrastructure_error (safety net if retries
exhaust, so a suspiciously-clean result is never silently trusted).
Deliberately not wired into is_model_retryable_error (cross-model
fallback): switching LLM models cannot change local sandbox boot
timing.

Verified: new tests/test_strix_caido_bootstrap_timing_retry.py (6
tests) pass; existing tests/test_strix_nvidia_nim_not_found_fallback.py
(11 tests) unaffected; shellcheck clean; full
scripts/ci/test_strix_quick_gate.sh suite passes (test_strix_quick_gate:
PASS, exit 0).
@seonghobae

Copy link
Copy Markdown
Contributor Author

Follow-up: same-model retry for the Caido sandbox bootstrap timing race

Stacked a second, related Strix CI-resilience fix onto this PR (commit 1ba2af4b):

While watching this fix propagate through the org's PR queue, fast-mlsirm#1081 and fast-mlsirm#1228 both hit a different Strix failure mode:

Error during penetration test: loginAsGuest failed after 10 attempts: curl exit 7:
curl: (7) Failed to connect to 127.0.0.1 port 48080 after 0 ms: Could not connect to server

Confirmed via gh api search/issues on usestrix/strix as a live, open upstream bug, not a misconfiguration on our side — strix-agent==1.5.3 is the current PyPI release, and our workflow skips no setup step (the sandbox image was already pulled and ready before the failure):

strix_quick_gate.sh already retries the same model up to STRIX_TRANSIENT_RETRY_PER_MODEL times for rate-limit / LLM-connection / service-unavailable / midstream-fallback errors, but had no classifier for this signature, so it fell straight through to a hard gate exit 1 on first occurrence. Added is_caido_bootstrap_timing_error(), wired into is_transient_same_model_retry_error (retry — the Docker image is already cached, so this is cheap and usually clears a one-off boot race) and has_detected_infrastructure_error (safety net if retries exhaust). Deliberately not wired into is_model_retryable_error (cross-model fallback): switching LLM models cannot change local sandbox boot timing.

Verified: new tests/test_strix_caido_bootstrap_timing_retry.py (6 tests) pass; existing tests/test_strix_nvidia_nim_not_found_fallback.py (11 tests) unaffected; shellcheck clean; full scripts/ci/test_strix_quick_gate.sh suite passes.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Retry docstring undercounts error families

The docstring for is_transient_same_model_retry_error states "Four error families qualify" and lists four, but the function now also retries on is_caido_bootstrap_timing_error. The comment no longer matches the code.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +272 to 289
is_private=""
for target_visibility_attempt in 1 2 3 4 5 6; do
if is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"; then
break
fi
is_private=""
if [ "$target_visibility_attempt" -lt 6 ]; then
echo "Repository visibility lookup failed (attempt ${target_visibility_attempt}/6), possibly a transient GitHub API rate limit; retrying after backoff." >&2
sleep "$(( target_visibility_attempt * 5 ))"
fi
done
case "$is_private" in
true | false) ;;
*)
echo "::error::Target repository visibility did not resolve to true or false."
echo "::error::Target repository visibility did not resolve to true or false after retries."
exit 1
;;
esac

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Visibility retry loop fails closed correctly

The retry assigns inside an if, so set -euo pipefail does not abort on a failed lookup. On exhaustion is_private is reset to empty and the case exits 1. The emitted is_private output is unchanged, so contract tests reading it still pass.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 2686 to +2691
if is_midstream_fallback_error; then
return 0
fi
if is_caido_bootstrap_timing_error; then
return 0
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Caido classifier sits behind timeout precedence

is_transient_same_model_retry_error returns non-retryable if is_timeout_error matches first. The observed Caido log has no timeout string, so it reaches the Caido branch. If a Caido run also emitted Strix run timed out after, timeout precedence would suppress the retry. Unlikely, and consistent with existing policy.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

…lookup-rate-limit-retry

# Conflicts:
#	CHANGELOG.md
…lookup-rate-limit-retry

# Conflicts:
#	CHANGELOG.md

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +2940 to +2947
is_caido_bootstrap_timing_error() {
if grep -Fq 'loginAsGuest failed after' "$STRIX_LOG" &&
grep -Eq 'Failed to connect to 127\.0\.0\.1 port [0-9]+' "$STRIX_LOG"; then
return 0
fi

return 1
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Caido classifier stays same-model-retry only

is_caido_bootstrap_timing_error feeds is_transient_same_model_retry_error and has_detected_infrastructure_error but not is_model_retryable_error, so an exhausted same-model retry budget fails closed instead of switching models. The earlier is_timeout_error guard does not intercept it, since the observed log carries a connect failure rather than a timeout string.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant