Skip to content

fix: handle Guzzle 8 network and response timeout exceptions - #458

Merged
gjtorikian merged 2 commits into
workos:mainfrom
wadakatu:fix/guzzle8-network-exceptions
Oct 23, 2026
Merged

gjtorikian merged 2 commits into
workos:mainfrom
wadakatu:fix/guzzle8-network-exceptions

Conversation

@wadakatu

Copy link
Copy Markdown
Contributor

Description

Since #427 the SDK allows Guzzle 8, but HttpClient::request() still catches only ConnectException and RequestException. Guzzle 8 classifies transport failures more finely (CurlFactory::createRejection()), so some of them now reach the caller unretried and unmapped:

Failure Guzzle 7 Guzzle 8 SDK result on Guzzle 8 before this PR
Timeout before response headers (cURL 28) ConnectException NetworkTimeoutException raw Guzzle exception, no retry
Empty reply / send / recv failure (cURL 52, 55, 56) ConnectException (52) or RequestException without a response (55, 56) NetworkException raw Guzzle exception, no retry
Timeout after response headers (cURL 28) ConnectException ResponseTimeoutException BaseRequestException with the interrupted response's status (e.g. 200) and its partial body as the message

NetworkException and NetworkTimeoutException extend TransferException without being a ConnectException or RequestException, so neither catch matches them. ResponseTimeoutException is a ResponseException, so it is mapped as an API error.

This PR:

  • catches PSR-18's NetworkExceptionInterface and ResponseTimeoutException in the branch that used to catch ConnectException, so these failures are retried and mapped to ConnectionException / TimeoutException as they were on Guzzle 7. ConnectException implements NetworkExceptionInterface on both majors, and on Guzzle 7 it is the only implementation, so the caught set on Guzzle 7 is unchanged.
  • classifies NetworkTimeoutException and ResponseTimeoutException as TimeoutException. (The 'timed out' message fallback already matched cURL's text; this makes it explicit, like the existing ConnectTimeoutException check.)

Retrying ResponseTimeoutException matches what the SDK did on Guzzle 7, where the same failure was a ConnectException. HandlerClosedException (also a bare TransferException on Guzzle 8) is left as is.

Tests: four cases in tests/HttpClientTest.php (timeout before headers, timeout after headers, network failure, network failure retried). They skip on Guzzle 7, where these classes do not exist. ./script/ci passes on Guzzle 8.3.0 (php-cs-fixer, PHPStan, 395 tests), and the suite passes on Guzzle 7.16.0 with the four new tests skipped. Without the fix, the four tests fail on Guzzle 8.

Documentation

Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.

[ ] Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

Guzzle 8 no longer reports every transport failure as ConnectException.
A timeout before response headers is NetworkTimeoutException, cURL 52/55/56
are NetworkException, and a timeout after response headers is
ResponseTimeoutException. The first two extend TransferException without
being a ConnectException or RequestException, so they escaped
HttpClient::request() unretried and unmapped. The third was mapped to a
BaseRequestException carrying the status code and partial body of the
interrupted response.

On Guzzle 7 all of these were ConnectException. Catch PSR-18's
NetworkExceptionInterface (implemented by ConnectException on Guzzle 7 and by
NetworkException on Guzzle 8) and ResponseTimeoutException in the
ConnectException branch, and classify both timeout types as TimeoutException.
On Guzzle 7 the caught set is unchanged: ConnectException is the only
NetworkExceptionInterface implementation there.
@wadakatu
wadakatu requested review from a team as code owners October 23, 2026 07:26
@wadakatu
wadakatu requested a review from mattgd October 23, 2026 07:26
@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; the previous test coverage issue is fixed.

Summary

The PR retries Guzzle 8 network and response timeout failures and maps them to the SDK’s connection or timeout exceptions.

  • The SDK retries Guzzle 8 network failures and maps timeouts by type.

Reviews (2) · Last reviewed commit: "test: pin timeout classification to the ..." · Reviewed by Greptile

Comment thread tests/HttpClientTest.php Outdated
Both timeout tests used messages containing "timed out", which the message fallback in isTimeoutException() matches on its own. Use messages without it so the tests fail if the NetworkTimeoutException / ResponseTimeoutException checks are removed.
@gjtorikian

Copy link
Copy Markdown
Contributor

thanks !

@gjtorikian
gjtorikian merged commit ca02b5c into workos:main Oct 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants