sindresorhus/got · info · RetryError
ERR_RETRYING
ERR_RETRYING
Error message
Retrying
What it means
RetryError (code ERR_RETRYING) is an internal control-flow signal, not a real failure. It is thrown at source/as-promise/index.ts:167 inside the retry callback that an afterResponse hook invokes via its second argument (the `retry` function). When a hook calls `retry(updatedOptions)`, got constructs a RetryError to unwind the current response-processing loop and restart the request with the new options. Because the promise wrapper catches it internally and re-issues the request, a caller should never observe it on the rejected promise — seeing it surface means the retry plumbing leaked out of the wrapper, typically because the hook was registered against the stream API (which has no retry loop) instead of the promise API.
Source
Thrown at source/as-promise/index.ts:167
options.stripUnchangedCrossOriginState(previousState!, changedState, {clearBody: !hasExplicitBody});
} else {
options.stripSensitiveHeaders(previousUrl, nextUrl, updatedOptions);
if (!isSameOrigin(previousUrl, nextUrl) && !hasExplicitBody) {
options.clearBody();
}
}
}
}
// Remove any further hooks for that request, because we'll call them anyway.
// The loop continues. We don't want duplicates (asPromise recursion).
// Unless preserveHooks is true, in which case we keep the remaining hooks.
if (!preserveHooks) {
options.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
}
throw new RetryError(request);
}));
if (!(is.object(response) && is.number(response.statusCode) && 'body' in response)) {
throw new TypeError('The `afterResponse` hook returned an invalid value');
}
}
} catch (error: unknown) {
request._beforeError(normalizeError(error));
return;
}
globalResponse = response;
if (!isResponseOk(response)) {
request._beforeError(new HTTPError(response));
return;
}
View on GitHub (pinned to e3924aa1e5)
Solutions
- Use the promise API (got(url), got.get, etc.) rather than got.stream(...) when you rely on afterResponse-driven retries — the RetryError unwinding only works inside asPromise.
- Ensure the afterResponse hook always returns either a response object or the result of calling retry(); never throw the RetryError yourself or re-emit it from an 'error' listener.
- If you wrap got with a custom handler (extend({ handlers })), make sure your handler awaits the inner promise rather than returning it partially, so the retry loop can complete before your handler resolves.
Example fix
// before — using stream API, retry callback has nowhere to unwind
await got.stream('https://api', {
hooks: { afterResponse: [(r, retry) => retry({ headers: { authorization: token } })] }
});
// after — promise API, RetryError is consumed internally
await got('https://api', {
hooks: { afterResponse: [(r, retry) => retry({ headers: { authorization: token } })] }
}); Defensive patterns
Strategy: validation
Validate before calling
// RetryError is internal — never expose it. If you saw it, you used the wrong API.
// Validate the call site uses the promise API when afterResponse retry is in play:
function assertSupportsRetryHook(client) {
if (typeof client.stream === 'function' && client === got.stream) {
throw new Error('afterResponse retry callback is only supported on the promise API, not got.stream');
}
} Type guard
import {RetryError} from 'got';
function isRetryError(error: unknown): error is RetryError {
return error instanceof Error && (error as any).code === 'ERR_RETRYING' && (error as any).name === 'RetryError';
} Try / catch
// If you wrap got with a custom handler, swallow leaked RetryErrors defensively.
try {
await wrappedClient(url);
} catch (error) {
if (error instanceof Error && (error as any).code === 'ERR_RETRYING') {
// internal control-flow leaked out — re-issue via the promise API
return got(url, options);
}
throw error;
} Prevention
- Always use the promise API (got(url)) when afterResponse hooks drive retries; never got.stream(...).
- Do not re-throw RetryError from custom error handlers or 'error' event listeners.
- Keep custom got.extend handlers transparent — await the inner promise and return its result unchanged.
When it happens
Trigger: Triggered when an afterResponse hook calls its second argument (the retry function) to force a retry, e.g. `hooks: { afterResponse: [(response, retry) => retry({ headers: { authorization: newToken } })] }`. The RetryError is created in as-promise/index.ts:167 and is normally swallowed by the surrounding `try/catch` at line 174 that feeds it back into makeRequest.
Common situations: Token-refresh hooks that retry with a new Authorization header, hooks that retry on a 429 after reading Retry-After, or migrating a hook from the promise API to the stream API (got.stream(...)) where the retry callback is unsupported. Also seen when a custom error handler re-throws the internal RetryError instead of letting the wrapper consume it.
Related errors
- The reassigned stream body must be readable. Ensure you prov
- The `afterResponse` hook returned an invalid value
- A retry listener has been attached already.
- ECONNRESET
- beforeCache hooks must be synchronous. The hook returned a P
AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03).
Data as JSON: /data/errors/ea8e1dee78d23d6b.json.
Report an issue: GitHub.