Skip to content

fix: preserve the underlying error when a reqwest request fails without a status#2669

Merged
Sebastian Thiel (Byron) merged 1 commit into
GitoxideLabs:mainfrom
ameyypawar:fix/2140-reqwest-error-source
Jun 23, 2026
Merged

fix: preserve the underlying error when a reqwest request fails without a status#2669
Sebastian Thiel (Byron) merged 1 commit into
GitoxideLabs:mainfrom
ameyypawar:fix/2140-reqwest-error-source

Conversation

@ameyypawar

Copy link
Copy Markdown
Contributor

What

Addresses #2140. When the blocking reqwest HTTP backend failed a request without an HTTP status — a connection or TLS failure, e.g. using http-client-reqwest without a TLS feature against an https:// URL — it stringified the reqwest::Error (err.to_string()) before wrapping it in an io::Error. A String has no source(), so the error chain dead-ended at "An IO error occurred when talking to the server", hiding the real cause — the empty .source() the reporter observed.

The outer wrapper (client::Error::Io(#[from] io::Error)) already preserves the io::Error as its source; the only break was this stringification.

This resolves the part Sebastian Thiel (@Byron) identified — "what I'd be missing here is a clearer error message". It does not make plain http-client-reqwest perform HTTPS (that still requires a TLS feature); it makes the reason a request failed visible instead of swallowed.

How

In the no-status branch, keep the reqwest::Error as the io::Error's source via io::Error::other(err) rather than stringifying it. The HTTP-status branch is unchanged (it still reports Received HTTP status N). .source() now walks into reqwest's chain and surfaces the underlying cause.

Tests

Adds a regression test (tests/blocking-transport-http-reqwest.rs) that drives a refused TCP connection through the reqwest backend and asserts the resulting error preserves its source(). This covers the no-status (connection-failure) path specifically — the existing reqwest http tests (run via blocking-transport.rs) already cover status-based errors like 401/404/500. It's wired as a [[test]] with required-features = ["http-client-reqwest", "maybe-async/is_sync"] so it builds and runs only under the reqwest test configuration.

Verification

  • The new regression test passes, and fails before the fix by construction (pre-fix source() is None; post-fix Some).
  • cargo clippy -p gix-transport --features http-client-reqwest,maybe-async/is_sync --tests and cargo fmt --check are clean; Cargo.lock is unchanged (no dependency change).
  • The Some(status) branch was only refactored (same kind, same message), so the existing http status tests are unaffected.

Scope is reqwest-specific — the reporter confirmed the curl backend works.

…ut a status

When the blocking `reqwest` HTTP backend failed without an HTTP status --
e.g. a connection or TLS failure, as when `http-client-reqwest` is used
without a TLS feature against an `https://` URL -- it stringified the
`reqwest::Error` before wrapping it in an `io::Error`. That dead-ended
`source()`, so callers only saw "An IO error occurred when talking to the
server" with no way to reach the real cause.

Keep the `reqwest::Error` as the `io::Error`'s source (via
`io::Error::other`) so the underlying cause is preserved. The HTTP-status
branch is unchanged.

Adds a `reqwest`-only regression test that drives a refused connection and
asserts the error source is retained; it runs under the
`http-client-reqwest` test configuration, which also adds http test
coverage for that backend.
@Byron Sebastian Thiel (Byron) force-pushed the fix/2140-reqwest-error-source branch 2 times, most recently from 82dea98 to 1228b87 Compare June 23, 2026 03:00

@Byron Sebastian Thiel (Byron) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks a lot!

This is a rare PR as I couldn't change a thing 😁.

Here my local results:

Before

❯ cargo run -p gix --example clone --features blocking-http-transport-reqwest https://github.com/samvv/evcape.git delme
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/examples/clone 'https://github.com/samvv/evcape.git' delme`
Url: "https://github.com/samvv/evcape.git"
Cloning "https://github.com/samvv/evcape.git" into "delme"...
Error: An IO error occurred when talking to the server

Caused by:
    error sending request for url (https://github.com/samvv/evcape.git/info/refs?service=git-upload-pack)

After

❯ cargo run -p gix --example clone --features blocking-http-transport-reqwest https://github.com/samvv/evcape.git delme
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/examples/clone 'https://github.com/samvv/evcape.git' delme`
Url: "https://github.com/samvv/evcape.git"
Cloning "https://github.com/samvv/evcape.git" into "delme"...
Error: An IO error occurred when talking to the server

Caused by:
    0: error sending request for url (https://github.com/samvv/evcape.git/info/refs?service=git-upload-pack)
    1: client error (SendRequest)
    2: connection closed before message completed

@Byron Sebastian Thiel (Byron) merged commit c8fbf29 into GitoxideLabs:main Jun 23, 2026
56 of 62 checks passed
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.

2 participants