sindresorhus/got · error · Error

A retry listener has been attached already.

Error message

A retry listener has been attached already.

What it means

Thrown at source/core/index.ts:384 from a `newListener` event handler on the Request stream. The Request reserves the `retry` event for its own internal use — the promise wrapper (as-promise) attaches a single retry listener to drive makeRequest. If a second listener for `retry` is added, got throws immediately because the retry orchestration assumes exactly one consumer; duplicate listeners would cause duplicate retry attempts or double-makeRequest calls.

Source

Thrown at source/core/index.ts:384

				for (const [header, value] of Object.entries(source.headers)) {
					const normalizedHeader = header.toLowerCase();

					if (omittedPipedHeaders.has(normalizedHeader) || connectionListedHeaders.has(normalizedHeader)) {
						continue;
					}

					if (!this.options.shouldCopyPipedHeader(normalizedHeader)) {
						continue;
					}

					this.options.setPipedHeader(normalizedHeader, value);
				}
			}
		});

		this.on('newListener', event => {
			if (event === 'retry' && this.listenerCount('retry') > 0) {
				throw new Error('A retry listener has been attached already.');
			}
		});

		try {
			this.options = new Options(url, options, defaults);

			if (!this.options.url) {
				if (this.options.prefixUrl === '') {
					throw new TypeError('Missing `url` property');
				}

				this.options.url = '';
			}

			this.requestUrl = this.options.url as URL;

			// Publish request creation event
			publishRequestCreate({

View on GitHub (pinned to e3924aa1e5)

Solutions

  1. Do not attach your own `retry` listener — use the `retry` option (retry.limit, retry.statusCodes, retry.methods) and the `afterResponse` retry callback instead.
  2. If you proxy events between emitters, exclude `retry` from the forwarded event list (got's own proxiedRequestEvents at as-promise/index.ts:30 is the reference list).
  3. Use the hooks API (hooks.beforeRetry, hooks.afterResponse) for any retry-side logic you need.

Example fix

// before
const stream = got.stream('https://api');
stream.on('retry', (count, error) => console.log('retrying', count));

// after — use the retry hook instead
await got('https://api', {
  retry: { limit: 3 },
  hooks: { beforeRetry: [(options, error, retryCount) => console.log('retrying', retryCount)] }
});
Defensive patterns

Strategy: validation

Validate before calling

// Ensure you never attach your own 'retry' listener. Use hooks instead.
const forbidden = ['retry'];
function assertNoInternalListeners(request, event) {
  if (forbidden.includes(event)) {
    throw new Error(`Do not attach '${event}' on a got Request — use the retry option or hooks.beforeRetry.`);
  }
}

Try / catch

try {
  request.on('retry', handler);
} catch (error) {
  if (error instanceof Error && /retry listener has been attached already/.test(error.message)) {
    throw new Error('Use hooks.beforeRetry or the retry option instead of request.on("retry", ...)', { cause: error });
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling `request.on('retry', ...)` yourself on a Request object that got already manages, or attaching two retry listeners. Most commonly hit by users of the stream API (got.stream) who try to hook retry manually, or by code that forwards events from one request to another and inadvertently forwards `retry` too.

Common situations: Custom event-forwarding wrappers, proxying all events from an inner request to an outer emitter (the library itself uses a guarded proxiedRequestEvents list that deliberately omits `retry`), or older tutorials that predate the promise-based retry API advising `.on('retry', ...)`.

Related errors


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