Skip to content

Fix TypeError in Http::retry() when callback receives null (fixes laravel/framework#59012)#6

Closed
JoshSalway wants to merge 2 commits into12.xfrom
fix/http-retry-when-null-exception-12x
Closed

Fix TypeError in Http::retry() when callback receives null (fixes laravel/framework#59012)#6
JoshSalway wants to merge 2 commits into12.xfrom
fix/http-retry-when-null-exception-12x

Conversation

@JoshSalway
Copy link
Owner

@JoshSalway JoshSalway commented Mar 18, 2026

Summary

Fixes laravel#59012 -- Http::retry() throws a TypeError when the retry callback has a typed Exception or Throwable parameter and the response has a non-error status code (like 302). The toException() method returns null for non-error responses, but the callback receives null where it expects an exception object.

Root Cause

The bug exists because Response::toException() returns null when the response status is not a client/server error (4xx/5xx). The retry logic calls call_user_func($this->retryWhenCallback, $response->toException(), ...) unconditionally, passing null as the first argument. When the callback has a type-hinted parameter like fn (Exception $e) => ..., PHP throws a TypeError because null is not an Exception.

This affects any retry configuration where the server returns redirects (302), informational (1xx), or other non-error responses. The retry callback fires on every response, not just errors.

Why This Fix Works

This fix works because it extracts $response->toException() into a variable and adds a null check: $this->retryWhenCallback && $exception ? call_user_func(...) : true. When the exception is null (meaning the response is not an error), the retry logic defaults to true (don't retry -- the request succeeded). This prevents the null value from ever reaching the typed callback parameter. The same fix is applied to both synchronous send() and asynchronous handlePromiseResponse() code paths.

Alternatives Considered

We chose this approach over wrapping the callback in a try-catch for TypeError because: (1) silencing a TypeError would mask legitimate bugs in user callbacks; (2) the semantic intent is clear -- a non-error response should not trigger retry logic at all; (3) defaulting to true (don't retry) for non-error responses matches the expected behavior -- you only retry failures.

Files Changed

  • src/Illuminate/Http/Client/PendingRequest.php -- Null-guard on toException() result in both sync and async retry paths
  • tests/Http/HttpClientTest.php -- 2 new tests for typed Exception and Throwable callback parameters with non-error responses

JoshSalway and others added 2 commits March 19, 2026 04:41
…xception

Verifies that Http::retry() does not throw TypeError when the when
callback type-hints Exception or Throwable and the response is a 3xx
status code (where toException() returns null).

Refs laravel#59012

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Http::retry() receives a non-failed response (e.g. 3xx),
toException() returns null. Passing null to a when callback that
type-hints Exception or Throwable causes a TypeError.

Skip calling the when callback when there is no exception to pass,
defaulting to the same behavior as having no when callback.

Fixes laravel#59012

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant