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-returning

View on GitHub (pinned to 6bcbee4fd9)

Solutions

  1. Use a regular function (not arrow) so `this.skip` binds correctly.
  2. In async tests, call `this.skip()` and `return` immediately; don't continue executing code afterward.
  3. For promise-based tests, prefer `ctx.skip()` via `it` context or mark with `it.skip` when possible.
  4. 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

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.