{"id":"1738538688b3f216","repo":"webpack/webpack","slug":"callback-the-callback-was-already-called","errorCode":null,"errorMessage":"callback(): The callback was already called.","messagePattern":"callback\\(\\): The callback was already called\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/loaders/LoaderRunner.js","lineNumber":217,"sourceCode":" * @param {LoaderContext} context the loader context\n * @param {EXPECTED_ANY[]} args arguments\n * @param {LoaderCallback} callback callback\n * @returns {void}\n */\nfunction runSyncOrAsync(fn, context, args, callback) {\n\tlet isSync = true;\n\tlet isDone = false;\n\tlet isError = false; // internal error\n\tlet reportedError = false;\n\n\t/**\n\t * @param {...EXPECTED_ANY} callbackArgs callback args\n\t * @returns {void}\n\t */\n\tfunction innerCallback(...callbackArgs) {\n\t\tif (isDone) {\n\t\t\tif (reportedError) return; // ignore\n\t\t\tthrow new Error(\"callback(): The callback was already called.\");\n\t\t}\n\n\t\tisDone = true;\n\t\tisSync = false;\n\n\t\ttry {\n\t\t\tcallback(...callbackArgs);\n\t\t} catch (err) {\n\t\t\tisError = true;\n\t\t\tthrow err;\n\t\t}\n\t}\n\tcontext.callback = innerCallback;\n\n\tcontext.async = function async() {\n\t\tif (isDone) {\n\t\t\tif (reportedError) return; // ignore\n\t\t\tthrow new Error(\"async(): The callback was already called.\");","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/webpack/webpack/blob/318421ea8ac81371f5171236a6efb63675576528/lib/loaders/LoaderRunner.js#L199-L235","documentation":"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.","triggerScenarios":"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.","commonSituations":"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)`.","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."],"exampleFix":"// before (loader.js)\nmodule.exports = function loader(content) {\n  this.callback(null, content);\n  doMore().then(() => this.callback(null, transformed)); // second call!\n};\n// after\nmodule.exports = function loader(content) {\n  this.callback(null, content);\n  return; // never call callback again\n};","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["loaders","callback","async"],"analyzedSha":"318421ea8ac81371f5171236a6efb63675576528","analyzedAt":"2026-08-03T19:39:56.731Z","schemaVersion":2}