Request
Please consider backporting 48fc340a66b8cd5605ba764ecaae4b27a1af23b5 (PR #5448, "use default CURL_BUFFERSIZE", merged 2026-04-08, fixing #5445) to release/6.2 and release/6.3.
Verified with git branch -r --contains: the fix is present on main and release/6.4.x only. Both release/6.2 and release/6.3 still contain the two easyHandle.set(preferredReceiveBufferSize: Int.max) call sites in HTTPURLProtocol.swift and FTPURLProtocol.swift.
Why this is worth a backport
The bug makes swift package resolve unusable on Linux against any Swift package registry served over HTTP/2, which is the common deployment (any CDN-fronted registry). It is not an edge case and there is no client-side workaround for SwiftPM users.
Measured on a real package tree (84 dependencies), cold cache, resolving from a registry that returns 303 to presigned storage URLs over HTTP/2:
| toolchain |
swift package resolve |
swift:6.1.3-noble |
exit 0 |
swift:6.2.0-noble |
crash |
swift:6.2 (6.2.4) |
crash |
swift:6.2-jammy |
crash |
swift:6.3.3-noble |
crash |
swiftlang/swift:nightly-6.4.x-jammy |
exit 0 |
FoundationNetworking/EasyHandle.swift:293: Fatal error: 'try!' expression unexpectedly
raised an error: Error Domain=libcurl.Easy Code=43 "(null)"
_HTTPURLProtocol.configureEasyHandle(for:body:)
_NativeProtocol.startNewTransfer(with:)
_HTTPURLProtocol.didCompleteRedirectCallback(_:)
closure #1 in _HTTPURLProtocol.redirectFor(request:)
It is not recoverable by retrying — a crashed attempt completes zero downloads, so a retry loop never converges (verified over 12 consecutive attempts). Every current stable Swift is affected; only the 6.4 development line is not.
Scope and risk
The change is a two-line deletion of two call sites. The set(preferredReceiveBufferSize:) method itself is untouched by the backport, and its remaining caller in WebSocketURLProtocol.swift runs before any transfer starts, so it is unaffected. Removing the call restores libcurl's default buffer size.
Reproduction
Minimal, no SwiftPM required — N concurrent URLSession.shared.downloadTask against a URL that redirects, where the first hop negotiates HTTP/2:
- N <= 4 survives, N >= 8 crashes (matches
httpMaximumConnectionsPerHost default of 6).
- Redirect status code is irrelevant: 301, 302, 303, 307, 308 all crash.
- The same test with the first hop forced to HTTP/1.1 (
http2 off) survives at N=85. Plaintext HTTP/1.1 also survives. Only HTTP/2 + a redirect fails.
One note on the root cause
The rationale given in #5445/#5448 — that Int.max is out of libcurl's accepted range so the call "always crashes" — does not appear to be the actual mechanism. set(preferredReceiveBufferSize:) clamps with min(size, Int(CFURLSessionMaxWriteSize)), so the value passed is 16384, which is inside libcurl's accepted range; and a single non-redirecting request works fine on the affected toolchains.
The trigger appears to be libcurl's guard in lib/setopt.c:
case CURLOPT_BUFFERSIZE:
if(data->state.buffer)
return CURLE_BAD_FUNCTION_ARGUMENT;
state.buffer is released at the end of multi_done(), which returns early when CONN_INUSE(conn) is true — i.e. when other easy handles are still attached to the same connection, which happens under HTTP/2 multiplexing. Since corelibs performs redirects itself (it never sets CURLOPT_FOLLOWLOCATION), the redirect reconfigures a handle whose buffer was never freed.
This does not change the correctness of the fix — removing the call avoids the guard entirely — but it explains why the crash requires both HTTP/2 and a redirect, and why value-based workarounds do not help.
Related: the regression was introduced between 6.1.3 and 6.2 by 4a9694d396b3 (#5159), which corrected header-include ordering so that LIBCURL_VERSION_MAJOR is defined. That enabled CURLMOPT_MAX_HOST_CONNECTIONS (previously inert), capping connections at httpMaximumConnectionsPerHost = 6 and causing excess transfers to be multiplexed onto an existing HTTP/2 connection. That commit is correct in itself; it activated a latent crash.
Request
Please consider backporting
48fc340a66b8cd5605ba764ecaae4b27a1af23b5(PR #5448, "use default CURL_BUFFERSIZE", merged 2026-04-08, fixing #5445) torelease/6.2andrelease/6.3.Verified with
git branch -r --contains: the fix is present onmainandrelease/6.4.xonly. Bothrelease/6.2andrelease/6.3still contain the twoeasyHandle.set(preferredReceiveBufferSize: Int.max)call sites inHTTPURLProtocol.swiftandFTPURLProtocol.swift.Why this is worth a backport
The bug makes
swift package resolveunusable on Linux against any Swift package registry served over HTTP/2, which is the common deployment (any CDN-fronted registry). It is not an edge case and there is no client-side workaround for SwiftPM users.Measured on a real package tree (84 dependencies), cold cache, resolving from a registry that returns
303to presigned storage URLs over HTTP/2:swift package resolveswift:6.1.3-nobleswift:6.2.0-nobleswift:6.2(6.2.4)swift:6.2-jammyswift:6.3.3-nobleswiftlang/swift:nightly-6.4.x-jammyIt is not recoverable by retrying — a crashed attempt completes zero downloads, so a retry loop never converges (verified over 12 consecutive attempts). Every current stable Swift is affected; only the 6.4 development line is not.
Scope and risk
The change is a two-line deletion of two call sites. The
set(preferredReceiveBufferSize:)method itself is untouched by the backport, and its remaining caller inWebSocketURLProtocol.swiftruns before any transfer starts, so it is unaffected. Removing the call restores libcurl's default buffer size.Reproduction
Minimal, no SwiftPM required — N concurrent
URLSession.shared.downloadTaskagainst a URL that redirects, where the first hop negotiates HTTP/2:httpMaximumConnectionsPerHostdefault of 6).http2 off) survives at N=85. Plaintext HTTP/1.1 also survives. Only HTTP/2 + a redirect fails.One note on the root cause
The rationale given in #5445/#5448 — that
Int.maxis out of libcurl's accepted range so the call "always crashes" — does not appear to be the actual mechanism.set(preferredReceiveBufferSize:)clamps withmin(size, Int(CFURLSessionMaxWriteSize)), so the value passed is 16384, which is inside libcurl's accepted range; and a single non-redirecting request works fine on the affected toolchains.The trigger appears to be libcurl's guard in
lib/setopt.c:state.bufferis released at the end ofmulti_done(), which returns early whenCONN_INUSE(conn)is true — i.e. when other easy handles are still attached to the same connection, which happens under HTTP/2 multiplexing. Since corelibs performs redirects itself (it never setsCURLOPT_FOLLOWLOCATION), the redirect reconfigures a handle whose buffer was never freed.This does not change the correctness of the fix — removing the call avoids the guard entirely — but it explains why the crash requires both HTTP/2 and a redirect, and why value-based workarounds do not help.
Related: the regression was introduced between 6.1.3 and 6.2 by
4a9694d396b3(#5159), which corrected header-include ordering so thatLIBCURL_VERSION_MAJORis defined. That enabledCURLMOPT_MAX_HOST_CONNECTIONS(previously inert), capping connections athttpMaximumConnectionsPerHost= 6 and causing excess transfers to be multiplexed onto an existing HTTP/2 connection. That commit is correct in itself; it activated a latent crash.