RunService:IsRunMode() always returns 'true', even when using 'Play Here'/'Play' in Roblox Studio?

When testing a game within Roblox Studio only, the RunService:IsRunMode() method always returns true in a server-sided script, regardless of any of the three possible ways to start game from within Roblox Studio; ‘Play’, ‘Play Here’ & ‘Run’.

But according to the API documentation for IsRunMode, only when using the green ´Run´ button, IsRunMode() should return true - so shouldn’t it then return false (also in a server-sided script) when using the ‘Play’ and ‘Play Here’ buttons?

Roblox Studio only enters run mode when the ‘Run’ button is pressed, not the ‘Play’ button.

How to experience this “bug”
Create an empty game, using nothing more than the baseplate template, and then add the following server- and client-scripts:

-- (server) Script - placed in ServerScriptService
local RunService = game:GetService("RunService")
print("(S)IsStudio =", RunService:IsStudio())
print("(S)IsRunMode=", RunService:IsRunMode()) 
print("(S)IsServer =", RunService:IsServer())
print("(S)IsClient =", RunService:IsClient())
print("(S)IsRunning=", RunService:IsRunning())

-- (client) LocalScript - placed in StarterPlayer/StarterCharacterScripts
local RunService = game:GetService("RunService")
print("(C)IsStudio =", RunService:IsStudio())
print("(C)IsRunMode=", RunService:IsRunMode()) 
print("(C)IsServer =", RunService:IsServer())
print("(C)IsClient =", RunService:IsClient())
print("(C)IsRunning=", RunService:IsRunning())

Then go through starting the game three times, using the ‘Run’, then ‘Play Here’ and last the ‘Play’ button, and notice the results in Output-panel. (Below output’s layout is set to be easier to read):

Run
  (S)IsStudio = true
  (S)IsRunMode= true
  (S)IsServer = true
  (S)IsClient = false
  (S)IsRunning= true

Play Here
  (S)IsStudio = true    (C)IsStudio = true
  (S)IsRunMode= true    (C)IsRunMode= false
  (S)IsServer = true    (C)IsServer = false
  (S)IsClient = false   (C)IsClient = true
  (S)IsRunning= true    (C)IsRunning= true

Play
  (S)IsStudio = true    (C)IsStudio = true
  (S)IsRunMode= true    (C)IsRunMode= false
  (S)IsServer = true    (C)IsServer = false
  (S)IsClient = false   (C)IsClient = true
  (S)IsRunning= true    (C)IsRunning= true

As can be seen, the server-sided script’s call to RunService:IsRunMode() always return true, and not follow what the description at the API documentation page specifies it should do.

13 Likes

I guess this is because technically, run mode is just a server with 0 clients connected, but it would make sense to have this return false when in APS.

I’m bumping this thread because I just came across this quirk. The current behavior is opposite to the name of the function and its documentation. For the method to work correctly, I must start a test server which is significantly slower to start than solo play.

What context did you experience this problem in where you wanted the function to work differently?

Because in my experience, the behavior that the function currently has is the most useful behavior, in that the server view in a Play session is very similar to the view in a Run session. So for instance any plugin that wants to do something specific in Run mode probably also wants to do the same thing in the Server view of Play. (That is to say, the current behavior is correct, and the current documentation is wrong).

2 Likes

I’m not saying this is an optimal workflow, but it’s what I was doing:

Starting a Run session is quicker than starting a Play session, so I was testing code on the server with Run that would eventually belong on the client. Periodically, I would move it to the client to make sure it still worked, which is a hassle. My solution was to put the code in a module in ReplicatedStorage and have the server and client both call it. That way, either the server would call it in Run or the client would call it in Play. However, the code runs twice in Play because IsRunMode() returns true for the server.

1 Like

Am I missing something or do you just want to be using IsClient / IsServer in this case instead? The Run session does have IsServer = true.

Sorry to bump this, but this is still broken. I need to be able to tell whether the game was started using the Run button or Play button, but this is currently not possible due to this bug.

To reproduce, open a new place, add a script, and copy this code into the script:

print(game:GetService("RunService"):IsRunMode())

It will always print true, whether you use the Run button or Play button.

Wouldn’t you use RunService:IsServer() for that instead?

3 Likes

If this behavior is more useful, could a new function be added to RunService that returns true if the game was started from the Run button? That’s what I thought IsRunMode was supposed to do, but if not, could a new function be added to for that?

1 Like

I saw this post and decided to do some testing myself. I went on a Roblox Studio build from 2017 and RunService:IsRunMode() works as the name suggests (and how the current documentation describes it).

I am pretty sure this change (intentional or not) was caused by the Accurate Play Solo feature introduced in 2018. On the 2017 build, IsRunMode() returned true only in true run mode and false everywhere else.

Here is a truth table comparison I made for the RunService:Is*() functions (hopefully I didn’t get anything wrong):

Looking at the truth table, my guess is that the “IsRunMode” state is getting set if the script is running in the context of the server DataModel instead of basing it on the run button itself, which defeats the original purpose of the function. While this is not a big deal, as code logic around play vs run mode has few use cases, it does break the consistency and purpose of the function and I think it needs to be addressed.

While that initially seems true, the behavior you describe is already possible by using the logical expression not IsClient() and IsStudio(), and so the current behavior actually removes possible functionality by now being redundant instead of like it was previously. If IsRunMode() changed back to the original behavior it would allow for a script to gain the additional ability to detect whether it was running in a run session specifically, and vice versa.

In my opinion, it would be nice for IsRunMode() to be fixed to the original behavior. If that can’t be done or doesn’t seem warranted, I think it should at least be deprecated. It is redundant with the other functions, the API docs are wrong, it can be confused with the existing IsRunning() function (especially with incorrect docs), and it has few use cases for non-plugin scripts with the introduction of APS.

You can use RunService:IsStudio() and then determine if there is a player in the game since play mode will have a player, and run mode will not. You can use the code below to detect it, but I wouldn’t suggest it as it is hacky and doesn’t work for local server mode. I’m not sure what your specific use case is, but you should try and find a different method to accomplish whatever you’re doing instead of relying on run vs play mode.

local RS = game:GetService("RunService")
local Players = game:GetService("Players")

--note: this can return false on the server during startup
--		you can use DeferForPlayMode() instead in that case
local function IsPlayMode()
	return RS:IsStudio() and RS:IsRunning() and (RS:IsClient() or #Players:GetPlayers() > 0)
end

--Defer a function to run only in play mode (non-blocking)
local function DeferForPlayMode(func, ...)
	if not RS:IsStudio() or not RS:IsRunning() then return end
	local asyncFunc
	if RS:IsServer() and #Players:GetPlayers() == 0 then
		--Wait for a possible 'late' join during server startup
		asyncFunc = coroutine.wrap(function(...)
			Players.PlayerAdded:Wait()
			func(...)
		end)
	else
		asyncFunc = coroutine.wrap(func)
	end
	asyncFunc(...)
end

--Example usage for above
DeferForPlayMode(function()
	--code that will only run during play mode
	print("Hello from play mode")
end)
8 Likes

This is a very well detailed reply, and I think you have everything on point. I agree with :IsRunMode()'s functionality being restored.

2 Likes

While this isn’t about RunMode(), It could be since that could also be affected here;

Requiring from a module on the Script Editor causes IsClient() to always be true even on a Server Script; The auto-complete feature, (the one that shows you the functions under a Object) can have problems with that depending on the module, if you don’t load the module for clients, then it will not show you anything because IsClient() is true;

TLDR- Script Editor simulates a sceneraio when caching modules on which even if the script is a server, it would think it’s a client and that can cause problems with modules;

Sadly this is still an issue today. I’m trying to create some automated tests that would require the server to know if it should be expecting a player. Due to the value being always true, the testing script cannot properly determine if a player is connecting. This makes the “IsRunMode” function no different than “IsStudio.” Hopefully this gets fixed soon.

Still a problem. I expect this function to return true only when I press “Run” under Test, hence “Run Mode”. My use case is I want to show debugging information in a voxel game on the client-side and in run mode, and for the server, it parents the gui to StarterGui and runs the code outside of there. Even if this is expected and is just poor naming of API, it would be nice to have some way to figure this out, without needing to rely on players unexpectedly joining. (that’s at least what I took from reading this post)

image

Currently, if I play with at least 1 client, it still counts as run mode. My temporary solution is to make sure IsStudio and IsServer is true, as it’s practically the same functionality for game scripts, and then remove the server-sided gui once any players join. I delete it on the client in case if it somehow gets pushed to the player’s PlayerGui before PlayerAdded fires.


Server Code

Client Code

1 Like

We need a method for detecting if Run is pressed, it is way more efficient to press Run over pressing Play and teleporting to visuals when testing visual stuff. In my case it is an obby