feat(stack): retry transient HTTP failures in slim services downloads - #6689
Conversation
Native artifact preparation failed on the first non-2xx response, so a single gateway error from the release host aborted a whole stack start. Retry rate limits, gateway errors, and dropped transfers with jittered backoff, and keep checksum mismatches and missing assets terminal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zn4vRJ2rkx5UXwNS1ndnj
There was a problem hiding this comment.
🤖 AI Review
Both independent reviews were available. After deduplication, five findings are confirmed and one is uncertain. The confirmed concerns are overly broad HttpClientError retries, missing coverage for checksum and partial-stream retries, wall-clock backoff in the integration test, invisible retry progress, and discarded Retry-After guidance. The Schedule.upTo attempt-cap concern remains uncertain because the pinned Effect source and installed type declarations are absent, so its accepted options and exact attempt semantics could not be verified.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | packages/stack/src/preparation/SlimServicesSource.ts:101 |
api-usage |
claude | Schedule.upTo({ times: 4 }) may either be invalid for the pinned Effect API or permit four retries rather than four total attempts; the repository has no test establishing the intended cap. |
| 🟡 MINOR | packages/stack/src/preparation/SlimServicesSource.ts:93 |
error-handling |
claude | The retry predicate accepts every HttpClientError reason, so deterministic client failures can consume the full retry schedule instead of failing immediately. |
| 🟡 MINOR | packages/stack/src/preparation/slim-services.integration.test.ts:203 |
test-coverage |
claude+codex | The integration test does not exercise the new checksum fetch retry or recovery from an archive stream failure after partial output has been written. |
| 🟡 MINOR | packages/stack/src/preparation/SlimServicesSource.ts:83 |
rate-limiting |
claude+codex | 429 responses are retried using the generic exponential schedule while the server's Retry-After guidance is discarded. |
| ⚪ NIT | packages/stack/src/preparation/SlimServicesSource.ts:99 |
testability |
claude | The hard-coded production backoff forces the new live integration test to wait on real jittered delays. |
| ⚪ NIT | packages/stack/src/preparation/SlimServicesSource.ts:352 |
observability |
claude | Retries provide no application-level progress or log signal, so callers remain in the downloading state throughout backoff and repeated attempts. |
Findings outside the diff
- ⚪ NIT
packages/stack/src/preparation/SlimServicesSource.ts:352— Retries provide no application-level progress or log signal, so callers remain in the downloading state throughout backoff and repeated attempts.
Stats
Claude findings: 6 · Codex findings: 2 · Confirmed: 5 · Refuted: 0 · Uncertain: 1
Models: claude-opus-5 + gpt-5.6-sol · Trigger: auto · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
Retry only transport failures and transfers cut mid-body, so deterministic request faults no longer consume the schedule, and cap attempts through Effect.retry's times option. Each retried transfer logs its URL and cause. The backoff is injectable, so coverage pins the retry budget, the checksum and truncated-archive recovery paths, and single-attempt 404s without waiting on jittered delays. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zn4vRJ2rkx5UXwNS1ndnj
|
I don't believe this one belongs to this PR, and the log says why.
This is the same shape as the wake failures seen across several PRs earlier today, where Spending the single re-run on this job to confirm. If it fails again on the same commit, I'll treat it as this PR's and dig further. Generated by Claude Code |
Summary
Native artifact preparation failed on the first non-2xx response, so a single gateway error from the slim-services release host aborted a whole stack start. Today that took
Testred across three unrelated PRs:github.com/supabase/slim-services/releases/download/...answered HTTP 504 for about an hour, on whichever asset was requested, and the failure cascaded from artifact preparation intoComposition start had failures.Artifact requests now retry transient failures with jittered exponential backoff, and keep deterministic failures single-attempt.
Key changes:
transferFaultclassifies retryable failures: HTTP 408, 429, and 5xx, plusHttpClientErrorwhose reason isTransportError(connect-level) orDecodeError(a transfer cut mid-body).StatusCodeError,EncodeError,InvalidUrlError, andEmptyBodyErrorfail on the first attempt.PreparationErrorcarries an optionalstatus, so classification reads the response code rather than parsing a message.Effect.retry({ schedule, times: TRANSFER_MAX_RETRIES, while })— 4 retries, so 5 attempts, over a 500ms jittered exponential delay. This matches the mechanism inapps/cli/src/commands/bootstrap/bootstrap.retry.ts.fetchBytesanddownloadToFileretry throughwithTransferRetry, which logs the URL and cause of each retried transfer.downloadToFileretries the whole transfer, so an attempt reopens the sink in truncating mode and replaces any partial download instead of appending to it. Digest comparison stays outside the retry, keeping a checksum mismatch terminal.makeSlimServicesSourceandslimServicesChecksum, so retry coverage runs without waiting on real delays. The attempt cap is not injectable.Linked issue
None; this came out of a CI incident rather than a filed issue.
Checklist
pnpm check:allpasses; relevant package tests pass for every touched workspace, andpnpm types:checkpasses for each touched TypeScript workspace.https://claude.ai/code/session_019zn4vRJ2rkx5UXwNS1ndnj