MongoDB transactions need a replica set, and localhost was not one
Every transactional path failed against a perfectly healthy local MongoDB. The database was fine. It just was not a replica set, and transactions do not work without one.

MongoDB transactions only run against a replica set or a sharded cluster, never against a standalone mongod, and a default local install is a standalone mongod. Every transactional path in the product failed the same way against it: the driver rejects the session with the message that transaction numbers are only allowed on a replica set member or mongos. Nothing was wrong with the database, the schema or the code. The deployment shape was wrong, and it was wrong in a way that looked healthy from every direction except the one that mattered.
A MongoDB transaction is built on the same machinery that replicates writes between members: the operations inside a session are assigned transaction numbers and coordinated through the oplog, which a standalone server does not maintain in the form the protocol needs. That is why the failure is a flat rejection rather than a degraded mode. There is no partial support to fall back to, so the driver refuses the session up front rather than starting something it cannot finish.
The practical consequence is that a single-node replica set is a supported and ordinary configuration, not a workaround. One container, one member, one call to rs.initiate() at first start — and the transactional API becomes available. Nothing about it is a cluster in any meaningful sense; the replica set is being run for the coordination machinery rather than for the redundancy.
The failure is not a connection error, which is what makes it confusing to chase. The connection succeeds. Ordinary reads and writes succeed. Indexes build. Health checks that ping the server come back fine. What fails is every code path that opens a session and starts a transaction, and it fails immediately with the same message every time — transaction numbers are only allowed on a replica set member or mongos. A test suite touching only simple reads will be entirely green while the paths that matter are all broken.
| What you check | Standalone mongod | Single-node replica set |
|---|---|---|
| Connect and ping | Works | Works |
| Ordinary find and insert | Works | Works |
| Building indexes | Works | Works |
| Opening a session | Works | Works |
| Starting a transaction in that session | Rejected every time | Works |
| rs.status().myState | Command unavailable | 1, meaning PRIMARY |
Which paths break depends entirely on what the application uses a transaction for, and the ones in this product are the paths where a half-applied write would be worst: claiming an approval so one approval cannot fund two actions, honouring an idempotency key so

The migration was not about query syntax. It was about what Prisma had been enforcing for free, and about writing each of those guarantees back by hand.

Starting the dev watcher over and over across a long session, and never killing the old ones, filled the machine's file descriptor table. The thing that fell over first was Docker, which had not done anything wrong.
Market4 turns one release note into a changelog page, a blog post, a mail-out and a week of social posts — and then tells you which of them brought anyone back.
Turning a local MongoDB into a single-node replica set takes two things: start mongod with a replica set name, and initiate the set once. In this product's Docker setup mongod runs with the arguments --replSet rs0 and --bind_ip_all, and a separate one-shot container waits for the server to answer a ping, then runs rs.initiate() if and only if the set has no configuration yet. That condition is what makes it safe to run on every deploy and every restart: an already-initiated set is left alone rather than reset.
The last two are the ones that catch people out. Without directConnection on a single-node set the driver may spend its whole server-selection window looking for members that do not exist, and a health check that only tests the port will report a healthy container that cannot run a transaction — which puts you straight back in the original confusion. The check worth writing is the one that asserts the member's state is PRIMARY.
The lesson from this class of bug is that a development environment can differ from production in its deployment shape rather than in its data, and shape differences are the ones that survive every test you thought to write. A missing row shows up in a fixture. A standalone server pretending to be a replica set shows up only when a specific API is called, which may be in a code path nobody exercises locally. The defence is to make the local environment match the production shape — same server arguments, same connection string parameters, same health check — rather than matching only its contents.
Because a default local MongoDB is a standalone server, and transactions are not supported on one. They require a replica set or a sharded cluster, since the transaction protocol is built on the same coordination machinery that replicates writes between members. The driver rejects the session outright with a message saying that transaction numbers are only allowed on a replica set member or mongos, rather than degrading to a non-transactional write.
Yes. A one-member replica set is an ordinary MongoDB configuration, not a hack. It costs one container and one call to rs.initiate() at first start, and it gives you the full transactional API. You are running the replica set for the coordination machinery rather than for redundancy, which is exactly the right trade in a development or single-server deployment.
It tells the driver to talk to the address you gave it rather than treating that address as a seed and going looking for the rest of the set. On a single-node replica set reachable at one hostname that is what you want; without it the driver can spend its whole server-selection window trying to discover members that do not exist, which surfaces as a slow timeout rather than a clear error.
Because a standalone mongod and an uninitiated replica set member both answer on the port perfectly well, and both are useless to a transaction. Checking that the member's state is PRIMARY is the check that distinguishes a container that has finished becoming what you need from one that is merely running. Anything weaker reports healthy and leaves the original confusion in place.
It is when it is guarded. Initiating a set that already has a configuration should be skipped, not retried, so the standard shape is a one-shot container that waits for the server to answer a ping, checks whether a configuration exists, and calls rs.initiate() only if none does. Written that way it is safe on every restart, and the log line saying the set was already initiated is the normal output rather than an error.

Publishing a post sends two independent announcements. One IndexNow request reaches Bing, Yandex, Seznam, Naver and Yep. Google takes no part in IndexNow, so it is told by re-submitting the sitemap.