SoftShutdown
SoftShutdown is the one-line answer to update deploys: when Roblox shuts a server down, players teleport through a reserved transit server and back into a fresh public server instead of being dumped to the app. Combined with the DataDriver’s shutdown flush: data saves, players come back, the update deploys. The defining design decision: the whole flow carries zero persistent state — the only coordination between the two stages is a ChloeKernelTransit flag riding the teleport data.
-- src/Server/Bootstrap.luaulocal Root = game:GetService("ServerScriptService").ChloeKernelServerlocal SoftShutdown = require(Root.SoftShutdown)
return function(kernel) SoftShutdown.attach(kernel)endThat is the entire integration. attach deliberately no-ops in Studio — there is nothing to migrate, and Studio cannot teleport anyway.
Mental model
Section titled “Mental model”attach wires two stages into the same place. Which stage a server plays depends on what kind of server it is — every server gets Stage 1; reserved servers additionally run Stage 2.
Stage 1 — the closing server
Section titled “Stage 1 — the closing server”A BindToClose callback runs when Roblox shuts the server down:
- If nobody is on the server, return immediately — nothing to migrate.
- Publish
Kernel.SoftShutdownwith the player count on the bus. This fires before any teleport — it is your window to message players. TeleportService:ReserveServer(game.PlaceId)reserves a private server of the same place. If the reservation fails, warn and give up — players disconnect normally, exactly as they would without SoftShutdown.- Build
TeleportOptionswith the reserved server’s access code and teleport dataChloeKernelTransit = true, thenTeleportAsyncevery player into the transit server in one call. A failed teleport retries once after 1 second; a second failure gives up with a warning. - Wait for the players to actually leave, polling every 0.5s up to a 25-second deadline — Roblox hard-kills the server 30 seconds after
BindToClosefires, and the 5-second margin is headroom for everything else sharing that window.
Stage 2 — the transit server
Section titled “Stage 2 — the transit server”The reserved server is the same place with the same code, so the same attach call runs there. It detects that it is a transit candidate (game.PrivateServerId non-empty with PrivateServerOwnerId == 0 — a reserved server, not a player-owned private server) and bounces arrivals:
- For every player present and every player who joins, read
Player:GetJoinData().TeleportData. - Only players carrying the
ChloeKernelTransitflag bounce. Ordinary reserved servers — Matchmaking arenas, private servers — are left alone; this branch runs on them too, and the flag is what keeps it inert there. - After a 2-second grace delay,
TeleportAsync(game.PlaceId, { player })with no reserved-server option — Roblox’s matchmaker places the player on a fresh public server, which by now is running the new version. - Up to 3 attempts per player with growing backoff (2s, 4s); a player who leaves on their own stops the retries. Three failures warn and leave the player on the transit server.
Stage 2 does not early-return past Stage 1: a deploy can shut down a reserved server too, so transit servers also register the BindToClose migration. A player caught mid-transit during a second deploy just rides another hop.
Data flush ordering
Section titled “Data flush ordering”SoftShutdown and the DataDriver both register BindToClose callbacks, and Roblox gives all bound callbacks one shared 30-second window. The interplay is deliberate:
- The transit teleport removes players, which fires the normal leave path — session teardown, profile release, save. Most profiles persist through the ordinary save-on-leave flow triggered by the teleport itself.
- The DataDriver’s own
BindToCloseflush (releaseAll) catches whatever the leave path has not finished, waiting on its own 25-second deadline and logging any key whose release did not land. - Both deadlines sit at 25s inside the 30s hard kill; neither callback depends on the other having run first.
When the player lands on a fresh public server seconds later, the join is an ordinary rejoin: new session, fresh profile load. The closing server released the profile’s session lock during the flush; if the hop is faster than the release, the DataDriver’s session-lock handling applies as it would for any rapid rejoin — see DataDriver.
Messaging players
Section titled “Messaging players”The Kernel.SoftShutdown topic publishes synchronously before the reservation, so a subscriber can push a state message that reaches clients before the teleport fires:
-- Server: alongside SoftShutdown.attach(kernel)local Net = kernel:net()Net:defineState("ServerMessage", { "String" })
kernel.Bus:subscribe("Kernel.SoftShutdown", function(_, playerCount) Net:broadcastState("ServerMessage", "Updating — teleporting you to the new version…")end)Roblox additionally shows its own teleport screen during both hops. There is no built-in transit-server UI; if you want a branded “back in a moment” screen, detect the transit server client-side — the same two signals Stage 2 uses, read with the client-side teleport-data API:
-- src/Client/Bootstrap.luaulocal TeleportService = game:GetService("TeleportService")
return function(kernel) if game.PrivateServerId ~= "" and game.PrivateServerOwnerId == 0 then local Data = TeleportService:GetLocalPlayerTeleportData() if type(Data) == "table" and Data.ChloeKernelTransit then showTransitScreen("Update deployed — sending you back in a moment…") end endendThe stay is short — the bounce fires 2 seconds after arrival — so the screen only needs to cover a few seconds plus the outbound teleport.
Deploy-day runbook
Section titled “Deploy-day runbook”The first real update deploy is this feature’s acceptance test. Run it deliberately:
- Confirm both participants are wired in the server Bootstrap:
SoftShutdown.attach(kernel)and a DataDriver:attach(kernel)(the shutdown flush comes from the driver, not from SoftShutdown). - Publish the update.
- Shut down one server from the Creator dashboard’s server list — not Shut Down All Servers — and watch its players resurface on new servers.
- Watch server logs for the failure warnings, each of which means a specific fallback fired:
[SoftShutdown] ReserveServer failed,[SoftShutdown] transit teleport failed twice,[SoftShutdown] could not bounce <player> back to a public server, and the DataDriver’s unlanded-key log. - Spot-check one migrated player’s data on the new server.
- Migrate the rest. Roblox’s Migrate To Latest Update also runs
BindToCloseon each server, so the same flow covers it.
API reference
Section titled “API reference”| Member | Description |
|---|---|
SoftShutdown.attach(kernel) |
Wires both stages. No-op in Studio. No options, no handle — the module is a BindToClose participant, not a service you interact with afterward |
Bus topics
Section titled “Bus topics”| Topic | Args | Published when |
|---|---|---|
Kernel.SoftShutdown |
playerCount |
Update migration is about to teleport everyone out — before the reservation, before any teleport |
Gotchas & design notes
Section titled “Gotchas & design notes”- Every failure degrades to the status quo. Failed reservation, double-failed teleport, players stuck past the deadline — each path warns and falls back to what would have happened anyway: a normal disconnect. SoftShutdown can only improve on the default outcome, never worsen it.
- The transit flag is client-transported, and that is fine. Teleport data round-trips through the client, so a determined exploiter could forge
ChloeKernelTransit. The only thing the flag does is bounce its carrier from a reserved server to a public server — a free ride to matchmaking, which is where they could have gone anyway. Nothing trusts the flag for anything of value. - Player-owned private servers never bounce. The Stage 2 branch requires
PrivateServerOwnerId == 0; VIP servers keep their players. - The transit hop doubles as a version fence. Players cannot land back on an old-version public server: by the time the bounce fires, the closing servers are draining and the matchmaker fills servers running the new build.