webpack/webpack · error · Error

callback(): The callback was already called.

Error message

callback(): The callback was already called.

What it means

runSyncOrAsync enforces single-callback semantics: once a loader's callback (the inner callback returned by this.callback, or the implicit completion) has fired, any further callback() invocation throws. The `reportedError` early-return only suppresses duplicate error reports; a duplicate success/error after completion still throws. This is the classic loader-runner 'callback already called' error surfacing a loader bug.

Source

Thrown at lib/loaders/LoaderRunner.js:217

 * @param {LoaderContext} context the loader context
 * @param {EXPECTED_ANY[]} args arguments
 * @param {LoaderCallback} callback callback
 * @returns {void}
 */
function runSyncOrAsync(fn, context, args, callback) {
	let isSync = true;
	let isDone = false;
	let isError = false; // internal error
	let reportedError = false;

	/**
	 * @param {...EXPECTED_ANY} callbackArgs callback args
	 * @returns {void}
	 */
	function innerCallback(...callbackArgs) {
		if (isDone) {
			if (reportedError) return; // ignore
			throw new Error("callback(): The callback was already called.");
		}

		isDone = true;
		isSync = false;

		try {
			callback(...callbackArgs);
		} catch (err) {
			isError = true;
			throw err;
		}
	}
	context.callback = innerCallback;

	context.async = function async() {
		if (isDone) {
			if (reportedError) return; // ignore
			throw new Error("async(): The callback was already called.");

View on GitHub (pinned to 318421ea8a)

Solutions

  1. Audit the offending loader: ensure exactly one of { return a value, call this.callback } happens per execution path.
  2. After calling this.callback(...), immediately `return;` so no further code runs.
  3. If using this.async(), capture the returned cb and call it exactly once; never also return synchronously with a value.
  4. Update the third-party loader; this is a bug in the loader, not in your config.

Example fix

// before (loader.js)
module.exports = function loader(content) {
  this.callback(null, content);
  doMore().then(() => this.callback(null, transformed)); // second call!
};
// after
module.exports = function loader(content) {
  this.callback(null, content);
  return; // never call callback again
};
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: A loader calls this.callback(...) and then also returns a value (which triggers the implicit completion), or calls this.callback twice. Mixing this.async() with a synchronous return that also invokes the callback. A loader that forks/async-resolves and invokes callback from two code paths.

Common situations: Hand-written or buggy loaders that don't guard their callback path. Loaders that call this.callback(err) and then continue processing and call it again on success. Mixing `return result` with `this.callback(null, result)`.

Related errors


AI-assisted analysis of webpack/webpack@318421ea8a (2026-08-03). Data as JSON: /data/errors/1738538688b3f216.json. Report an issue: GitHub.