mongodb/node-mongodb-native · critical · MongoRuntimeError

illegal state transition from [${target.s.state}] => [${newS

Error message

illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]

What it means

Thrown by the state machine produced by makeStateMachine() when a requested transition (e.g. CONNECTING => CONNECTED) is not listed as legal from the current state. The driver uses three such machines (topology.ts, server.ts, monitor.ts) to enforce the SDAM lifecycle. Hitting this means the driver attempted an out-of-order lifecycle change, which is an internal invariant violation rather than normal user input. It surfaces as a MongoRuntimeError.

Source

Thrown at src/utils.ts:426

  s: { state: string };
  emit(event: 'stateChanged', state: string, newState: string): void;
}
interface StateTransitionFunction {
  (target: ObjectWithState, newState: string): void;
}

/** @public */
export type EventEmitterWithState = {
  /** @internal */
  stateChanged(previous: string, current: string): void;
};

/** @internal */
export function makeStateMachine(stateTable: StateTable): StateTransitionFunction {
  return function stateTransition(target, newState) {
    const legalStates = stateTable[target.s.state];
    if (legalStates && legalStates.indexOf(newState) < 0) {
      throw new MongoRuntimeError(
        `illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`
      );
    }

    target.emit('stateChanged', target.s.state, newState);
    target.s.state = newState;
  };
}

/**
 * This function returns the number of milliseconds since an arbitrary point in time.
 * This function should only be used to measure time intervals.
 * @internal
 * */
export function processTimeMS(): number {
  return Math.floor(performance.now());
}

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Call connect() exactly once per MongoClient and do not re-connect on an already-connected client; rely on a singleton.
  2. Serialize lifecycle calls: never call close() concurrently with connect(); await both fully.
  3. After any fatal connection error, create a fresh MongoClient rather than retrying connect() on the same instance.
  4. If this reproduces deterministically, capture the from/to states in the message and report it as a driver bug with a minimal repro.

Example fix

// before
await client.connect();
// ... later, another path:
await client.connect(); // illegal transition if already connected

// after
if (!client.isConnected()) {
  await client.connect();
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  if (!client.isConnected()) await client.connect();
} catch (err) {
  if (err instanceof MongoRuntimeError && /illegal state transition/.test(err.message)) {
    // abandon this client and build a fresh one
    client = new MongoClient(uri);
    await client.connect();
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Concurrent lifecycle calls racing against each other: calling connect() while a connect() is in flight, calling close() while connect() is still connecting, or connect() being invoked on an already-connected client/server/monitor. Also seen after a previous error left an object in an unexpected state.

Common situations: Calling `await client.connect()` twice without close(); invoking operations that internally connect while the application also explicitly connects; using the same MongoClient across workers/forks after the topology is partially torn down; race between request handlers triggering close() and new requests triggering connect().

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/91ead02f90d628e5.json. Report an issue: GitHub.