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

  1. 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.
  2. 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.
  3. 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

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


AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03). Data as JSON: /data/errors/ea8e1dee78d23d6b.json. Report an issue: GitHub.