Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Retry Middleware Laravel Package

caseyamcl/guzzle_retry_middleware

Guzzle Retry Middleware automatically retries Guzzle HTTP requests when servers return 429 or 503, or when requests time out. It honors Retry-After headers, backs off exponentially, supports configurable retry limits and callbacks, and drops into your handler stack with no setup.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. This is a Guzzle HandlerStack middleware, not a Laravel package — no service provider, no facade, no config publishing. That is a feature, not a gap: it sits at the transport layer beneath Illuminate\Http\Client, so it applies uniformly to controllers, jobs, console commands, and third-party SDKs that build on Guzzle. For a Laravel shop it cleanly complements, rather than overlaps with, Http::retry(), which handles connection failures and a fixed-millisecond backoff but does not negotiate Retry-After, does not support per-request elapsed-time ceilings, and does not have a status-code allowlist with a callback hook. The middleware's real value is policy-as-configuration: retry statuses, backoff multiplier, attempt caps, wall-clock budgets, method allowlists, and a pre-retry callback that receives the request and options by reference. That maps directly onto decisions a TPM usually has to arbitrate (how hard do we retry, against whom, and what do we log) and expresses them in one place instead of scattered try/catch blocks.

Integration feasibility. Low. A single composer require plus one push() onto a stack, or a shared client factory bound in the container. Options cascade three levels (request → client constructor → middleware factory), which supports sensible defaults with per-integration overrides — exactly the governance shape you want. No PHP extensions, no native dependencies, no runtime services.

Technical risk. Moderate, concentrated in four areas:

Risk Detail Severity
Version matrix README states v3 targets Guzzle 8+; Guzzle 6/7 requires pinning ^2. Laravel 10–12 ship Guzzle 7.x, so the default composer require may resolve a package incompatible with the framework's locked Guzzle. The package description ("Guzzle v6/7+") contradicts the README. High if unpinned
Blocking backoff Delays are implemented as in-process sleeps. Under PHP-FPM that is a worker held hostage; under Octane it is a stalled event-loop task. High
Idempotency retry_on_methods defaults to *. Retrying POST/PATCH against a non-idempotent endpoint can duplicate writes or double-charge. High
Adoption signal 222 stars, MIT, single maintainer, effectively no downstream dependents in the graph. Small surface (a few hundred lines, PHPStan level 8, near-100% coverage) makes it forkable, but you are the de facto maintainer of your fork. Medium

A secondary note: the README describes the default backoff as exponential, but the documented formula is multiplier × attempt number — that is linear growth. Do not size latency budgets off the "exponential" label.

Key questions to resolve before approval.

  1. Which Guzzle major does our Laravel lock resolve to, and are we pinning ^2 or ^3?
  2. Do we need Retry-After compliance and timeout-retry semantics, or does Http::retry() plus queue-level retries already satisfy the requirement? If both are used, they must never be stacked on the same call.
  3. What is our idempotency policy for non-GET/HEAD methods on retried endpoints?
  4. Where does retry belong — transport middleware, queue job retries, or a circuit-breaker layer in front of both?
  5. What are the per-integration attempt and wall-clock budgets (max_retry_attempts, give_up_after_secs, max_allowable_timeout_secs), and who approves exceptions?
  6. What retry telemetry do we require, and does the on_retry_callback hook satisfy it without additional instrumentation?

Integration Approach

Stack fit. The middleware slots beneath Laravel's HTTP client without touching application code paths. Two viable shapes:

  • Shared Guzzle client factory bound as a singleton, with the stack assembled in a service provider. Cleanest separation; works everywhere, including SDKs that take a ClientInterface.
  • Http::globalOptions() / per-request withOptions() to inject the handler or per-call retry options into Illuminate\Http\Client. Lower friction, but Guzzle options are unvalidated pass-throughs — a typo in retry_on_stauts fails silently and yields no retries, which is a nasty outage-time surprise. The factory approach lets you validate option keys in one place.

Recommend the factory, with a thin RetryPolicy config object sourced from config/services.php so retry behaviour is reviewable in a PR rather than buried in a controller.

Migration path. Additive and reversible. Nothing else changes; removing the middleware restores prior behaviour exactly. That makes it safe to pilot on one integration and roll back without code churn.

Compatibility. Confirm the resolved Guzzle major in composer.lock before writing any code — this is the single blocking check. Verify PHP version support, and confirm the app does not already register Guzzle's stock RetryMiddleware on the same stack. Confirm no existing Http::retry() calls target the same endpoints. Note that retried request bodies must be rewindable; streamed or non-seekable bodies will fail on the second attempt, so upload endpoints need retry_enabled => false.

Sequencing.

  1. Inventory outbound HTTP integrations: method, idempotency, SLA, whether the remote honours Retry-After.
  2. Pin the correct major (^2 for Guzzle 7, ^3 for Guzzle 8) and lock it.
  3. Build the client factory + config layer with validated option keys and conservative defaults (lower max_retry_attempts than the shipped 10; set give_up_after_secs; restrict retry_on_methods).
  4. Wire on_retry_callback to the Laravel logger and a metric counter.
  5. Pilot on one non-critical, idempotent integration; measure retry rate and added latency in staging.
  6. Roll out per-integration with explicit budget overrides.
  7. Publish a one-page retry standard so future endpoints inherit policy instead of improvising.

Operational Impact

Maintenance. Low ongoing engineering cost, low but non-zero vendor cost. Single-maintainer project with a changelog, tagged releases, static analysis, and high coverage — solid hygiene for its size. The realistic exposure is abandonment rather than bugs: budget a fork contingency. The code is small enough that a mid-level engineer can read the whole thing in an afternoon, which materially de-risks that scenario. Pin the version; do not float.

Support. No Laravel-specific documentation, so your team owns the mapping from Laravel's HTTP client to this middleware's options, plus the semantic decisions (what counts as retryable, what the budgets are). The on_retry_callback signature includes a nullable response and nullable exception, which is the correct shape for timeout-triggered retries but is easy to misuse — write one adapter and reuse it rather than letting each team write its own.

Scaling. This is the section that should drive the decision. Retries multiply load precisely when a dependency is already degraded; ten attempts per request turns a 2× traffic spike into a 20× one. Mitigations: set max_retry_attempts to 2–3 for user-facing synchronous paths, keep higher values only for async jobs; always set give_up_after_secs and max_allowable_timeout_secs so a hostile or buggy Retry-After value cannot pin a worker indefinitely; restrict retry_on_methods. Separately, the in-process sleep model does not scale with concurrency — on Octane or a small FPM pool, long backoffs consume capacity and can convert a dependency blip into a self-inflicted outage. Prefer short delays in the middleware and push long-horizon retries to the queue.

Failure modes to instrument for.

  • Non-idempotent retry causing duplicate side effects (payments, order creation, outbound webhooks).
  • Stacked retries — middleware plus Http::retry() plus queue retries — producing multiplicative attempt counts.
  • Unvalidated option typos silently disabling retries.
  • Retry-After values far beyond acceptable user latency; worker occupancy grows unbounded.
  • Non-rewindable request bodies failing on the second attempt.
  • Timeout retries doubling the effective latency budget on already-slow endpoints, breaching upstream SLAs.
  • Retries masking a persistent dependency failure that should trigger a circuit breaker or alert instead.

Ramp-up. Engineering ramp is roughly a day for a competent PHP developer — the API is small and the README is complete. The expensive part is organisational: agreeing an idempotency policy, per-integration budgets, and a telemetry standard. Plan one to two sprints for the policy work and treat the library integration itself as the cheap part. Net assessment: worth adopting for a controlled set of idempotent, non-latency-critical integrations where Retry-After compliance matters; not a blanket default across the whole application without the caps and allowlists above.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics