mochajs/mocha · warning · PendingError

sync skip; aborting execution

Error message

sync skip; aborting execution

What it means

`Runnable.prototype.skip()` marks the test as pending and throws a `PendingError` to immediately abort the test body's synchronous execution; Mocha's internal error handler recognizes it and reports the test as skipped rather than failed. Seeing this message as a raw failure means the pending sentinel escaped Mocha's handling.

Source

Thrown at lib/runnable.js:140

      return this._slow;
    }
    if (typeof ms === "string") {
      ms = milliseconds(ms);
    }
    debug("slow %d", ms);
    this._slow = ms;
    return this;
  }

  /**
   * Halt and mark as pending.
   *
   * @memberof Mocha.Runnable
   * @public
   */
  skip() {
    this.pending = true;
    throw new PendingError("sync skip; aborting execution");
  }

  /**
   * Check if this runnable or its parent suite is marked as pending.
   *
   * @private
   */
  isPending() {
    return this.pending || (this.parent && this.parent.isPending());
  }

  /**
   * Return `true` if this Runnable has failed.
   * @return {boolean}
   * @private
   */
  isFailed() {
    return !this.isPending() && this.state === Runnable.constants.STATE_FAILED;

View on GitHub (pinned to 6bcbee4fd9)

Solutions

  1. Prefer `it.skip`/`describe.skip` or `this.skip()` only inside a running test/hook where Mocha can intercept it.
  2. Don't catch-and-rethrow errors thrown inside test bodies without re-checking `err instanceof PendingError` (or checking `this.isPending()`).
  3. For conditional skipping in async tests, use the async pattern (`this.skip()` in a promise chain) or set `this.pending` via a helper that Mocha supports.
  4. If this surfaces as a test failure rather than a skip, check for custom wrappers/uncaught handlers swallowing or rethrowing PendingError.

Example fix

// before
wrap(function (done) {
  if (!support) this.skip(); // rethrown by wrapper -> surfaces as failure
});

// after
it('does x', function () {
  if (!support) return this.skip();
  // rest of test
});
Defensive patterns

Strategy: try-catch

Validate before calling

if (this.isPending?.()) return; // already skipped, don't call skip again
if (!ctx || typeof ctx.skip !== 'function') {
  throw new Error('this.skip() only valid inside a runnable test/hook');
}

Type guard

const isPendingError = (err) => err instanceof Error && (err.message === 'sync skip; aborting execution' || err.message === 'async skip; aborting execution');

Try / catch

try {
  runTestBody();
} catch (err) {
  if (isPendingError(err)) {
    markTestPending(test);
    return; // treat as skip, not failure
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `this.skip()` inside a test body or hook; Mocha's `--bail`/uncaught-exception plumbing failing to swallow the PendingError (e.g. `this.skip()` used where no runnable context exists); user code catching and rethrowing the sentinel across a boundary Mocha doesn't inspect.

Common situations: Dynamic skipping in tests (`if (!condition) this.skip()`); calling `this.skip()` inside a `before` hook of a suite-level skip; wrapping test functions in custom async wrappers that rethrow errors after Mocha's handler ran.

Related errors


AI-assisted analysis of mochajs/mocha@6bcbee4fd9 (2026-09-01). Data as JSON: /api/errors/9b5bdd208f2feea1. Report an issue: GitHub.