← Back to issue list

Source pull step doesn't retry on transient network failures

View original Github issue

Metadata

Project
craft-parts
Number
#1666
Type
issue
State
open
Author
lengau
Labels
—
Created
Updated
Closed
—

Current evaluation

SourceHandler.download() in craft_parts/sources/base.py and GitSource._run() issue single un-retried requests.get()/git clone, so transient network errors fail the whole pull step. Unlabelled, no maintainer comments.

Suggested action: needs triage

Reason: Verified the report against the pinned tree: craft_parts/sources/base.py:313 still calls `requests.get(self.source, stream=True, allow_redirects=True, timeout=3600)` inside a bare try/except that converts HTTPError to SourceNotFound/HttpRequestError and RequestException to NetworkRequestError, with no Session/Retry anywhere (a repo-wide grep for 'retry' hits only craft_parts/packages/snaps.py, os_utils.py umount, and npm_plugin's `curl --retry`, plus pytest flaky markers). The report is well evidenced with concrete CI run URLs and a specific proposed fix, but it carries no labels, zero comments, no assignee, and no maintainer acknowledgement — so it has not been triaged yet. Actionability is capped at 62 because the git-side half needs a maintainer decision on how to classify transient git failures (exit-code vs stderr-pattern matching), which the issue itself leaves open.

Evaluated by: mr-cal-bot

Impact: 55 Quick Win: 20 Complexity: 40 Confidence: 74 Actionability: 62

Issue body

> 🤖 This issue was created by an AI agent (GitHub Copilot CLI) on behalf of a maintainer, while investigating flaky CI failures. ## Description Source handlers in `craft_parts.sources` do not retry on transient network failures during the `pull` step. This affects at least: * `TarSource`/`ZipSource`/other HTTP-based sources: `SourceHandler.download()` in `craft_parts/sources/base.py` issues a single `requests.get()` call with no retry, backoff, or retry-aware session configured: ```python # craft_parts/sources/base.py try: request = requests.get( self.source, stream=True, allow_redirects=True, timeout=3600 ) request.raise_for_status() url_utils.download_request(request, self._file) except requests.HTTPError as err: ... raise errors.HttpRequestError(...) from err except requests.RequestException as err: raise errors.NetworkRequestError(...) from err ``` * `GitSource`: `_run()` in `craft_parts/sources/git_source.py` (via `os_utils.process_run` in `craft_parts/sources/base.py`) shells out to `git clone`/`git fetch` once, with no retry. A transient failure while cloning over HTTPS (e.g. the remote dropping the connection mid-transfer) surfaces as a raw `subprocess.CalledProcessError` that isn't even recognized as a network error. In both cases, a single transient hiccup — a `5xx` from the origin server or a CDN, a reset/dropped connection, a momentary DNS/network blip — fails the entire `pull` step (and therefore the whole build) immediately, with no attempt to retry. This affects any real user of craft-parts pulling a `source:` URL (tarball, zip, or git repo), not just CI — it's simply most visible here because we run the same source-fetching integration tests very frequently. ## Examples from CI history `TarSource.download()` (`tests/integration/plugins/test_autotools.py::test_autotools_plugin`, downloads `hello_2.10.orig.tar.gz` from `launchpadlibrarian.net`/`launchpad.net`): * https://github.com/canonical/craft-parts/actions/runs/31522566940/job/93883282233 (2026-08-11) — `craft_parts.sources.errors.HttpRequestError: Cannot process request (Proxy Error: 502)` * https://github.com/canonical/craft-parts/actions/runs/30815509120/job/91692141413 (main, 2026-08-03) — `OSError: [Errno 101] Network is unreachable` → `urllib3.exceptions.NewConnectionError` → `requests.exceptions.ConnectionError` → `craft_parts.sources.errors.NetworkRequestError` * https://github.com/canonical/craft-parts/actions/runs/29457111352/job/87492627401 (main, 2026-07-15) — `ConnectionResetError: [Errno 104] Connection reset by peer` → `requests.exceptions.ConnectionError` → `craft_parts.sources.errors.NetworkRequestError` This same test has failed this way at least a dozen times across the last two months (e.g. runs 83724396417, 83776466896, 83776517670, 83804529989, 83804534004, 83845909077, 85709682080, 86151387606, 87489004187, 87761987882, 88075999228, 88496850084, 89735086830, 89769147054, 90199824275, 91136822317, 91431838329, 91431842766, 94209797821, 94239946054), all with the same underlying `craft_parts.sources.errors.{Network,HttpRequest}Error` cause and no retry. `GitSource._run()` (`tests/integration/plugins/test_go.py::test_go_use_incomplete_parts`, `git clone` of `https://go.googlesource.com/sys`): * https://github.com/canonical/craft-parts/actions/runs/28273822918/job/83776472668 (2026-06-27) — `subprocess.CalledProcessError: ... 'git', 'clone', ... 'https://go.googlesource.com/sys' ... returned non-zero exit status 128` with `fatal: expected 'packfile'` in the git output (a transient failure mid-clone over HTTPS) We currently work around this class of flakiness at the *test* level with `@pytest.mark.flaky(reruns=..., only_rerun=[...])` on individual integration tests (see `tests/integration/packages/test_chisel.py`, `tests/integration/lifecycle/test_chisel_lifecycle.py`, `tests/integration/utils/test_process.py`, and now `tests/integration/plugins/test_autotools.py`). That masks the flakiness in our own CI, but does nothing for actual users of craft-parts who hit the same transient errors when pulling sources. ## Proposed solution Add real retry-with-backoff to the source `pull` implementations, e.g.: * For HTTP(S)-based sources (`SourceHandler.download()` in `craft_parts/sources/base.py`): use a `requests.Session` with a `urllib3.util.retry.Retry` configuration mounted via `HTTPAdapter`, retrying on connection errors and a documented set of transient status codes (`429, 500, 502, 503, 504`), with a small number of retries and exponential backoff. Keep the existing behavior for non-retriable errors (e.g. `404` → `SourceNotFound`) unchanged — only retry the transient subset. * For `GitSource` (and any other subprocess-based source, e.g. via `os_utils.process_run`): retry the clone/fetch command itself a small number of times on non-zero exit, possibly limited to cases where the output matches known-transient git/network error patterns (e.g. `early EOF`, `expected 'packfile'`, `RPC failed`, connection resets), similar to how `git` itself is sometimes wrapped with retry logic in other tooling. * Make the retry count/backoff configurable (or at least centralize it) so it can be tuned without touching every source implementation individually. This would fix the flakiness at its root rather than continuing to paper over it with `pytest.mark.flaky` on every integration test that happens to pull sources over the network.

Evaluation history

Date Model Scores Action Summary
stealth/space-bunny-alpha
Impact: 55
Quick Win: 20
Complexity: 40
Confidence: 74
Actionability: 62
needs triage SourceHandler.download() in craft_parts/sources/base.py and GitSource._run() issue single un-retried requests.get()/git clone, so transient network errors fail the whole pull step. Unlabelled, no maintainer comments.
stealth/space-bunny-alpha
Impact: 58
Quick Win: 22
Complexity: 52
Confidence: 90
Actionability: 78
needs triage HTTP and Git source pulls fail immediately on transient network errors. A detailed, unlabelled proposal to add bounded retries remains open without maintainer follow-up and needs triage.
qwen3.6-35b-a3b-mtp-q6
Impact: 75
Quick Win: 34
Staleness: 45
Complexity: 55
Confidence: 65
Support Request: 15
needs triage Feature request to add retry-with-backoff to source pull operations (HTTP and git) to handle transient network failures. Unlabelled, no maintainer response after 24 days.
qwen/qwen3.6-35b-a3b
Staleness: 0
Complexity: 65
Confidence: 85
Support Request: 0
needs triage Unlabelled issue requesting retry logic for transient network failures during source pulls in craft_parts. Author is a maintainer, but no maintainer response or labels yet. Needs triage.

Update history

Date Change
created

Related issues

No related issues found above the similarity threshold.