Inconsistency in Player.CharacterAdded's argument between server and client

Player.CharacterAdded behaves inconsistently depending on whether one connects to it in a Script or in a LocalScript. The argument received on a LocalScript is consistently an empty Model, while the argument received in a Script is a Model containing character parts and a Humanoid.

Reproduction steps
  1. Open an empty baseplate in Studio;
  2. Insert a Script in game.ServerScriptService with the following code:
local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(new_player)
	new_player.CharacterAdded:Connect(function(new_character)
		print(new_player.Name, "spawned:", #new_character:GetChildren())
	end)
end)
  1. Insert a LocalScript in game.StarterPlayer.StarterPlayerScripts with the following code:
local player = game:GetService("Players").LocalPlayer

player.CharacterAdded:Connect(function(new_character)
	print(player.Name, "spawned:", #new_character:GetChildren())
end)
  1. Playtest the game (through the F5 hotkey) and reset the character a few times.
  2. Open the output, and note how inconsistent results are shown depending on whether it’s from the LocalScript or the Script, and how these results remain the same when you reset the character: in the client output, the received argument has no descendants.

Publishing the place and playing it through the website also renders the same results.


This is unexpected, since the developer hub guarantees that instances such as parts and the Humanoid are there when the event is fired (emphasis added to relevant portion of the quote):

Note that the Humanoid and its body parts (head, torso and limbs) will exist when this event fires, but clothing items like Hats and Shirts , Pants may take a few seconds to be added to the character (connect Instance.ChildAdded on the added character to detect these).

(as the page does not explicitly list any differences between client and server behavior, I assume this should be expected in both cases.)

This happens in Studio and in live games, and has confirmedly existed since at least 2020-06-20, although it might have been around for much longer.

Some immediate consequences of this issue are:

  • I need to yield when manipulating character Humanoids or parts on the client as they are added, with calls to wait or WaitForChild or with a connection to ChildAdded, while I do not need to do this on the server.
  • This is not documented, so unsuspecting developers will face issues and will need to debug them, only to ultimately find this weird behavior.

Potential breaking effects fixing this issue might have:

  • Existing local code relying on ChildAdded/DescendantAdded on new characters to modify their parts or humanoids can break or yield indefinitely.
5 Likes

Thanks for the report! We’ve filed this internally and we’ll follow up here when we have an update for you.

1 Like

I am in the process of checking over bug reports and following up on some bugs that haven’t received any activity in a while.
Is this issue still occurring or can you confirm that this bug has been resolved?

I can confirm that this issue is still occurring as of Jan 9th 2021, on both R6 and R15 characters. Thanks for following up on this, and please let me know if any other information is needed.

Are there any updates on this? I just ran into this bug today and I can confirm that it’s causing issues. The information in the original post is still accurate.