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
- Audit the offending loader: ensure exactly one of { return a value, call this.callback } happens per execution path.
- After calling this.callback(...), immediately `return;` so no further code runs.
- If using this.async(), capture the returned cb and call it exactly once; never also return synchronously with a value.
- 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
- In a custom loader, after calling this.callback(...), immediately `return;`.
- Never both return a value and call this.callback in the same execution path.
- Capture this.async() once and invoke the resulting cb exactly once.
- Add a `let called = false;` guard inside the loader if its control flow is complex.
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
- async(): The callback was already called.
- request should be a string or object with loader and options
- experiments.typescript does not support .tsx/JSX. Use a TSX-
AI-assisted analysis of webpack/webpack@318421ea8a (2026-08-03).
Data as JSON: /data/errors/1738538688b3f216.json.
Report an issue: GitHub.