Retries, caching & rate limits
Retries
Reads (GET) retry a dropped connection, a 429 or a 5xx with backoff (retry.times attempts). A 4xx is never retried, and a write is never retried at all — a 5xx on a write may already have landed.
Client-side rate limiting
Every request goes through the http-client-rate-limits-for-laravel limiter. By default requests are paced — when a provider’s window is exhausted the call waits until it frees up rather than failing. With adaptive on (the default) the limiter also reads each provider’s own Retry-After / X-RateLimit-* headers and backs off exactly as the server asks. Set max_wait to fail fast instead:
GITHUB_RATELIMIT_MAX_WAIT=2000 # fail fast when a defer would exceed 2 s
GITHUB_RATELIMIT_JITTER=250 # spread retries by up to 250 ms
GITHUB_RATELIMIT_ENABLED=false # or turn the client-side limiter off entirelyA defer that would exceed max_wait throws RateLimitExceededException, which carries the wait as a retry hint through the toolkit’s HasRetryAfter contract:
use RoundlyConsulting\Git\Exceptions\RateLimitExceededException;
use RoundlyConsulting\PackageToolkit\Contracts\HasRetryAfter;
try {
Git::github()->repositories();
} catch (RateLimitExceededException $e) {
$e->retryAfterSeconds(); // the wait the limiter would have needed
}
// In your exception handler — works for any throttled Roundly package:
if ($e instanceof HasRetryAfter) {
return response('Too Many Requests', 429, ['Retry-After' => $e->retryAfterSeconds()]);
}The limiter defaults to an in-memory store — per process, fine for CLI and single-worker use. For a quota shared across workers or servers, point http-client-rate-limits.store at a cache, Redis or database store (HTTP_CLIENT_RATE_LIMITS_STORE); git does not force a store.
Server-reported status
The last response’s server-reported rate limit is exposed on the provider:
$status = Git::github()->rateLimit(); // ?RateLimitStatus { limit, remaining, used, resetAt }
$status?->remaining;
$status?->resetAt; // ?CarbonConditional caching and logging
With cache.enabled, ETags are stored and replayed via If-None-Match, and 304 responses are served from cache. Cache entries are keyed per credential, so two accounts never share one. Logging writes method, URL, status and duration at debug level, and never tokens or bodies:
GIT_CACHE_ENABLED=true
GIT_CACHE_STORE=redis
GIT_CACHE_TTL=3600
GIT_LOGGING_ENABLED=true
GIT_LOGGING_CHANNEL=stackShow your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.