{"id":"91ead02f90d628e5","repo":"mongodb/node-mongodb-native","slug":"illegal-state-transition-from-target-s-state","errorCode":null,"errorMessage":"illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]","messagePattern":"illegal state transition from \\[(.+?)\\] => \\[(.+?)\\], allowed: \\[(.+?)\\]","errorType":"exception","errorClass":"MongoRuntimeError","httpStatus":null,"severity":"critical","filePath":"src/utils.ts","lineNumber":426,"sourceCode":"  s: { state: string };\n  emit(event: 'stateChanged', state: string, newState: string): void;\n}\ninterface StateTransitionFunction {\n  (target: ObjectWithState, newState: string): void;\n}\n\n/** @public */\nexport type EventEmitterWithState = {\n  /** @internal */\n  stateChanged(previous: string, current: string): void;\n};\n\n/** @internal */\nexport function makeStateMachine(stateTable: StateTable): StateTransitionFunction {\n  return function stateTransition(target, newState) {\n    const legalStates = stateTable[target.s.state];\n    if (legalStates && legalStates.indexOf(newState) < 0) {\n      throw new MongoRuntimeError(\n        `illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`\n      );\n    }\n\n    target.emit('stateChanged', target.s.state, newState);\n    target.s.state = newState;\n  };\n}\n\n/**\n * This function returns the number of milliseconds since an arbitrary point in time.\n * This function should only be used to measure time intervals.\n * @internal\n * */\nexport function processTimeMS(): number {\n  return Math.floor(performance.now());\n}\n","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/3366c21a6311e02f1be91da982f9b93d3cce99a0/src/utils.ts#L408-L444","documentation":"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.","triggerScenarios":"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.","commonSituations":"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().","solutions":["Call connect() exactly once per MongoClient and do not re-connect on an already-connected client; rely on a singleton.","Serialize lifecycle calls: never call close() concurrently with connect(); await both fully.","After any fatal connection error, create a fresh MongoClient rather than retrying connect() on the same instance.","If this reproduces deterministically, capture the from/to states in the message and report it as a driver bug with a minimal repro."],"exampleFix":"// before\nawait client.connect();\n// ... later, another path:\nawait client.connect(); // illegal transition if already connected\n\n// after\nif (!client.isConnected()) {\n  await client.connect();\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  if (!client.isConnected()) await client.connect();\n} catch (err) {\n  if (err instanceof MongoRuntimeError && /illegal state transition/.test(err.message)) {\n    // abandon this client and build a fresh one\n    client = new MongoClient(uri);\n    await client.connect();\n  } else {\n    throw err;\n  }\n}","preventionTips":["Call connect() exactly once per MongoClient instance; guard with isConnected().","Never run connect() and close() concurrently on the same client.","After a fatal connection error, create a new MongoClient instead of reconnecting."],"tags":["sdam","lifecycle","concurrency","internal"],"analyzedSha":"3366c21a6311e02f1be91da982f9b93d3cce99a0","analyzedAt":"2026-08-04T13:40:15.335Z","schemaVersion":2}