mochajs/mocha · warning · PendingError
async skip; aborting execution
Error message
async skip; aborting execution
What it means
Inside async runnable execution, Mocha installs `this.skip` as an `asyncSkip` function that fires `done()` and throws a `PendingError` to halt the remaining function body. The thrown error is intentionally ignored by Mocha's uncaught handler so the test is reported as pending; it surfaces as a problem only when that sentinel escapes Mocha's bookkeeping.
Source
Thrown at lib/runnable.js:328
if (this.fn && typeof this.fn.call !== "function") {
done(
new TypeError(
"A runnable must be passed a function as its second argument.",
),
);
return;
}
// explicit async with `done` argument
if (this.async) {
this.resetTimeout();
// allows skip() to be used in an explicit async context
this.skip = function asyncSkip() {
this.pending = true;
done();
// halt execution, the uncaught handler will ignore the failure.
throw new PendingError("async skip; aborting execution");
};
try {
callFnAsync(this.fn);
} catch (err) {
// handles async runnables which actually run synchronously
errorWasHandled = true;
if (err instanceof PendingError) {
return; // done() is already called in this.skip()
} else if (this.allowUncaught) {
throw err;
}
done(Runnable.toValueOrError(err));
}
return;
}
// sync or promise-returningView on GitHub (pinned to 6bcbee4fd9)
Solutions
- Use a regular function (not arrow) so `this.skip` binds correctly.
- In async tests, call `this.skip()` and `return` immediately; don't continue executing code afterward.
- For promise-based tests, prefer `ctx.skip()` via `it` context or mark with `it.skip` when possible.
- Ensure no wrapper rethrows PendingError into Mocha as a normal failure; treat `err.message === 'async skip; aborting execution'` as a skip.
Example fix
// before
it('loads', async function () {
if (!server) this.skip(); // arrow/continued execution can leak the sentinel
await load();
});
// after
it('loads', async function () {
if (!server) return this.skip();
await load();
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (typeof this.skip !== 'function' || this.constructor === ArrowContext) {
throw new Error('async this.skip() requires a non-arrow function test with Mocha context');
} Type guard
const isPendingError = (err) => err instanceof Error && /skip; aborting execution/.test(err.message);
Try / catch
try {
await asyncTestBody();
} catch (err) {
if (isPendingError(err)) {
reportPending(test);
return;
}
throw err;
} Prevention
- Use function expressions (not arrows) so `this.skip` is available.
- In async tests, `return this.skip()` and stop execution immediately.
- Don't let generic promise `.catch()` handlers rethrow the sentinel.
- Prefer it.skip or a skip helper that sets pending without throwing when possible.
When it happens
Trigger: Calling `this.skip()` inside an async test's promise/`done` flow (`it('...', function(done){ ...; this.skip(); })`); using `this.skip()` inside a callback where the arrow-function binding loses `this`; custom promise chains rethrowing the PendingError after Mocha's handler ran.
Common situations: Conditional skipping in async tests; mixing arrow functions (`this` undefined so fallback paths misbehave); test frameworks (e.g. old webdriver/selenium wrappers) that intercept thrown errors before Mocha's uncaught handler.
Related errors
AI-assisted analysis of mochajs/mocha@6bcbee4fd9 (2026-09-01).
Data as JSON: /api/errors/421b31e1ce8c0b85.
Report an issue: GitHub.