Skip to content

fix(coverage-evidence): retry trusted-uv download on transient network errors - #953

Closed
seonghobae wants to merge 5 commits into
mainfrom
fix/trusted-uv-download-retry
Closed

fix(coverage-evidence): retry trusted-uv download on transient network errors#953
seonghobae wants to merge 5 commits into
mainfrom
fix/trusted-uv-download-retry

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

coverage-evidence (used by opencode-review-dispatch.yml to gate approval on every PR org-wide) downloads a fixed uv release archive from releases.astral.sh with zero retry. That single shared origin is fetched by every coverage-evidence run across the whole organization, so it occasionally answers a transient network/HTTP error under concurrent load even though the file itself is healthy.

Observed directly, not guessed: two independent occurrences on naruon, three days apart (PR #1293 on 2026-08-10, PR #1300 on 2026-08-13), both with the exact same log line:

##[error]Could not materialize base Python locks: trusted uv archive download failed: HTTPError

Both PRs were otherwise healthy — the failure produced a false-negative REQUEST_CHANGES from opencode-agent unrelated to either PR's actual content. This is also very likely the root cause of the two test_materialize_base_python_requirements.py-adjacent findings already tracked as "pre-existing, environment/timing-sensitive" failures from PR #949's cycle.

Fix

Split _download_trusted_uv_archive into:

  • _fetch_trusted_uv_archive_once — the single-attempt network sink, containing the entire unchanged trust boundary (redirect rejection via _RejectTrustedUvRedirects, exact host/port pin, bounded size, and the caller's checksum/member verification).
  • _download_trusted_uv_archive — a bounded-retry wrapper (3 attempts, short linear backoff) that retries only OSError (the network-level failure class, which includes HTTPError/URLError). Trust-boundary violations (RuntimeErrors: unsafe redirect, oversized payload) are raised on the first occurrence and are never retried.

Every retry attempt runs the identical trust boundary, so this adds resilience and does not weaken any check.

Tests

  • Updated the static AST security contract (test_trusted_uv_download_contract.py) to target the relocated network-sink function — same invariant, unchanged.
  • New/updated behavioral tests: transient-then-success (exact call count + backoff delay), retry exhaustion, redirect/oversized rejections never retried (exactly 1 call).
  • Full pytest tests/: confirmed via git stash that all remaining failures (13 opencode-model-pool, 5 linux-platform-guard) are byte-for-byte identical on unmodified main (macOS-local-only; pass on the pinned Linux CI runner) — no new regression.
  • interrogate: 100% docstring coverage on the changed file. Coverage: 100% reachable lines/branches outside the Linux-only platform guard, identical shape to unmodified main.

Test plan

  • pytest tests/test_materialize_base_python_requirements.py tests/test_trusted_uv_download_contract.py -q
  • pytest tests/ -q (full suite, confirmed no new failures vs. unmodified main)
  • interrogate -v scripts/ci/materialize_base_python_requirements.py
  • CI (CodeQL, gitleaks, pip-audit, Bandit, Semgrep, strix, opencode-review) — pending on this PR

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 개선 사항
    • 신뢰된 아카이브 다운로드 중 일시적인 네트워크 오류가 발생하면 최대 3회까지 자동 재시도합니다.
    • 재시도 사이에 지연 시간을 적용해 일시적 연결 문제 이후 다운로드가 성공할 수 있습니다.
    • 리디렉션, 호스트·포트, 파일 크기, 체크섬 및 아카이브 검증은 모든 시도에서 계속 수행됩니다.
    • 재시도 가능한 오류가 지속되면 명확한 실패 정보와 함께 다운로드가 중단됩니다.

…k errors

The shared releases.astral.sh origin is fetched by every coverage-evidence
run across the organization. A single transient HTTPError/URLError under
concurrent load previously failed the whole job with no retry, producing
false-negative REQUEST_CHANGES verdicts on otherwise-healthy PRs (observed
directly on naruon #1293 and #1300, three days apart, identical error).

Split _download_trusted_uv_archive into a single-attempt network sink
(_fetch_trusted_uv_archive_once) plus a bounded-retry wrapper (3 attempts,
short backoff) that only retries OSError. Trust-boundary violations
(unsafe redirect, oversized payload) remain RuntimeErrors raised on the
first attempt and are never retried. Updated the static AST security
contract test to target the relocated network-sink function (same
one-literal-URL invariant, unchanged). New tests cover: transient-then-
success, retry exhaustion, and that redirect/size rejections are never
retried.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 87 minutes

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?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b96226b4-9772-4837-93e5-b9db705a5034

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd33d7 and f175471.

📒 Files selected for processing (7)
  • AGENTS.md
  • ARCHITECTURE.md
  • CHANGELOG.md
  • CLAUDE.md
  • docs/doctoring/trusted-uv-transient-download-retry.md
  • scripts/ci/materialize_base_python_requirements.py
  • tests/test_materialize_base_python_requirements.py
📝 Walkthrough

Walkthrough

trusted-uv 아카이브 다운로드를 단일 시도 함수와 재시도 제어 함수로 분리했습니다. OSError만 최대 3회 재시도하며, 리다이렉트·출처·포트·크기 검증 오류는 즉시 실패합니다. 관련 테스트와 변경 로그를 갱신했습니다.

Changes

trusted-uv 다운로드 재시도

Layer / File(s) Summary
단일 시도 다운로드와 검증
scripts/ci/materialize_base_python_requirements.py, tests/test_trusted_uv_download_contract.py
단일 시도 함수가 네트워크 요청과 trusted-uv 검증을 수행합니다. AST 계약 테스트가 해당 함수를 확인합니다.
제한된 재시도와 회귀 검증
scripts/ci/materialize_base_python_requirements.py, tests/test_materialize_base_python_requirements.py, CHANGELOG.md
OSError 발생 시 최대 3회 재시도하고 지연 시간을 적용합니다. 크기 초과와 안전하지 않은 리다이렉트는 재시도하지 않습니다. 테스트와 변경 로그를 갱신했습니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 6bd33

The PR adds bounded retries for transient download failures while preserving trust checks. No actionable merge-blocking risk remains; the retry-delay assertions can be strengthened as a small follow-up.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 trusted-uv 다운로드의 일시적 네트워크 오류 재시도라는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/trusted-uv-download-retry

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/test_materialize_base_python_requirements.py (1)

552-567: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

모든 재시도 지연값을 검증하십시오.

Line 564는 time.sleep 인수를 버립니다. 현재 테스트는 호출 횟수만 확인합니다. 따라서 3회 실패 시 선형 지연 delay, delay * 2가 상수 지연으로 변경되어도 통과합니다.

수정 예시
     monkeypatch.setattr(materializer.urllib.request, "urlopen", _urlopen)
-    monkeypatch.setattr(materializer.time, "sleep", lambda _seconds: None)
+    sleeps: list[float] = []
+    monkeypatch.setattr(materializer.time, "sleep", sleeps.append)
     with pytest.raises(RuntimeError, match="download failed"):
         materializer._download_trusted_uv_archive()
     assert calls == materializer.TRUSTED_UV_DOWNLOAD_ATTEMPTS
+    assert sleeps == [
+        materializer.TRUSTED_UV_DOWNLOAD_RETRY_DELAY_SECONDS,
+        materializer.TRUSTED_UV_DOWNLOAD_RETRY_DELAY_SECONDS * 2,
+    ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_materialize_base_python_requirements.py` around lines 552 - 567,
Update
test_download_trusted_uv_archive_exhausts_retries_and_reports_download_failed to
record each value passed to time.sleep instead of discarding it, then assert the
complete retry-delay sequence matches the expected linear delays (delay and
delay * 2) for the failed attempts while preserving the existing call-count and
exception assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_materialize_base_python_requirements.py`:
- Around line 552-567: Update
test_download_trusted_uv_archive_exhausts_retries_and_reports_download_failed to
record each value passed to time.sleep instead of discarding it, then assert the
complete retry-delay sequence matches the expected linear delays (delay and
delay * 2) for the failed attempts while preserving the existing call-count and
exception assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e583fc22-519c-4eab-9b3f-602232328c47

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb06cd and 6bd33d7.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • scripts/ci/materialize_base_python_requirements.py
  • tests/test_materialize_base_python_requirements.py
  • tests/test_trusted_uv_download_contract.py

Record each backoff sleep on retry exhaustion and require delay then
2*delay. Keep Darwin installer tests on the linux x86_64 path. Cite
RFC 9110 for transient-only retries.
@seonghobae

Copy link
Copy Markdown
Contributor Author

Review follow-up on current head.

CodeRabbit nit is applied: exhaustion now asserts the linear backoff sequence `[delay, 2*delay]` against the shipped `time.sleep` calls. Trust-boundary violations remain unretried (1 call). Doctoring: `docs/doctoring/trusted-uv-transient-download-retry.md` (RFC 9110, APA 7th).

@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 02:55
CWE-755: the production urllib HTTPError is an OSError subclass and
must enter the bounded retry; RuntimeError trust-boundary failures
stay unretried.
A missing or forbidden archive is a client policy failure. Retry only
5xx, 429, and non-HTTP OSError so a 404 cannot be probed three times.
@seonghobae

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Review-only request for exact current head b983bb20; do not mutate or merge. Re-evaluate trusted-uv download retry: 5xx/429/OSError retry, first-attempt fail-closed on other 4xx. Independent current-head approval is required for the two-approval gate.

@opencode-agent opencode-agent 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.

Pull request overview

OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.

Findings

1. HIGH Current-head GitHub Checks - Fix failed required checks before approval

  • Problem: Failed same-head checks remain for b983bb20091933c2f604edcaee9a6b078775edd6.
  • Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
  • Fix: Read and fix the failed check logs below, then rerun the current-head checks.
  • Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.

Failed checks:

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (4 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (4 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs: trusted-uv-transient-download-retry.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: trusted-uv-transient-download-retry.md"]
  R2 --> V2["docs review"]
  Evidence --> S3["CI script: materialize_base_python_requirements.py"]
  S3 --> I3["review and security gate shell path"]
  I3 --> R3["Review risk: CI script: materialize_base_python_requirements.py"]
  R3 --> V3["bash -n plus Strix self-test"]
  Evidence --> S4["Test (2 files)"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test (2 files)"]
  R4 --> V4["targeted test run"]
Loading

@opencode-agent

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: b983bb20091933c2f604edcaee9a6b078775edd6
  • Workflow run: 31695376780
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.

Findings

1. HIGH Current-head GitHub Checks - Fix failed required checks before approval

  • Problem: Failed same-head checks remain for b983bb20091933c2f604edcaee9a6b078775edd6.
  • Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
  • Fix: Read and fix the failed check logs below, then rerun the current-head checks.
  • Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.

Failed checks:

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (4 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (4 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs: trusted-uv-transient-download-retry.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: trusted-uv-transient-download-retry.md"]
  R2 --> V2["docs review"]
  Evidence --> S3["CI script: materialize_base_python_requirements.py"]
  S3 --> I3["review and security gate shell path"]
  I3 --> R3["Review risk: CI script: materialize_base_python_requirements.py"]
  R3 --> V3["bash -n plus Strix self-test"]
  Evidence --> S4["Test (2 files)"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test (2 files)"]
  R4 --> V4["targeted test run"]
Loading

Materialize a base Python lock only when every package line is an exact
SHA-256 pin or a two-token relative -r/--requirement include of a
candidate lock path. A lone --require-hashes directive, ./dotted paths,
and -r other-hashes.txt no longer enter the trusted build context.

Copy link
Copy Markdown
Contributor Author

@opencode-agent @cwl-noema-review Review-only request for exact current head f175471a2f64adb297bd12039b3de6cdae918db1; do not mutate, approve on stale evidence, or merge. The predecessor CHANGES_REQUESTED review was tied to b983bb20091933c2f604edcaee9a6b078775edd6 and cancelled Strix jobs. On this exact head, Trusted uv Materializer Quality CI, Strix Changed Path Quality CI, CodeQL PR, Python Security, Security Scan, SAST Semgrep, Secret Scan, OSV-Scanner PR, Scorecard PR, and SBOM Generation are all terminal-success. Re-evaluate the bounded transient-download retry and its fail-closed trust boundary against the exact current diff. A qualifying independent non-author approval and branch protection remain mandatory.

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #790.

#790 implements the same buyer-visible transient trusted-uv download repair on a newer protected base, but with a narrower retry classifier (explicit transient transport conditions and HTTP 408/425/429/500/502/503/504 only), deterministic delays, immutable request reuse, trusted Git resolution, descriptor-relative no-follow output handling, symlink/hard-link/FIFO/TOCTOU defenses, full documentation, and the permanent 100% quality contract. Retrying every OSError as proposed here is materially broader and would also retry permanent HTTP failures. The useful retry/backoff tests from this branch are already represented in the authoritative branch's larger regression suite.

Do not merge both implementations; maintain one source of truth in #790.

@seonghobae seonghobae closed this Aug 14, 2026
auto-merge was automatically disabled August 14, 2026 08:06

Pull request was closed

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