The advice above is right and you should follow it, but nobody’s said why your current graph reads nothing, and I think it’s worth knowing because it’s a trap you’ll keep hitting until you’ve seen it once.
Your Client RPC is almost certainly being thrown away.
You’re firing this from the character’s BeginPlay. On the server, the order inside RestartPlayer is:
SpawnDefaultPawnFor() <- pawn spawns, BeginPlay runs here
Possess() <- controller takes the pawn, ownership established
BeginPlay happens before Possess. So at the moment your RPC_S_Startup_Info runs on the server, that character has no owning connection yet. “Replicated To Owning Client” with no owning client doesn’t queue or retry, it just gets dropped. No warning, no log, nothing.
That’s why you see no error and no result. The RPC isn’t failing, it’s going nowhere.
Same thing on the client side if BeginPlay beats the Controller being replicated in, a server RPC from an unowned pawn gets discarded too.
Quick way to confirm before you change anything: put a print on the first node of RPC_C_Startup_Info. If it never prints, that’s your answer.
The second thing, in your HUD graph
Your Create Character UI Widget node has the Owning Player pin empty. Fill it in with Get Owning Player Controller. Left empty it can resolve to something you didn’t expect, and anything in the widget that calls GetOwningPlayer later will come back null, which gives you exactly the “reads nothing” symptom again but from a different direction.
Why the PlayerState advice fixes this and not just because it’s tidier
The useful way to think about it is this. RPCs are order dependent. They fire once, at one moment, and if the thing on the other end isn’t ready, that’s it, you’ve lost it. You end up writing IsValid checks everywhere, and your IsValid on Main Widget Ref is doing exactly that, it’s quietly swallowing the failure with no retry path.
RepNotify is order independent. The value replicates, and whenever it lands the OnRep runs, whether that’s before or after your HUD exists. You stop racing.
So put health and team on the PlayerState as RepNotify variables, and have the UI read from there. PlayerState is replicated to everyone and it exists before the pawn does, so the timing problem disappears instead of being worked around.
For the UI, rather than pushing a reference into the widget from outside, pull from inside it. GetOwningPlayer, then PlayerState, then cast. Do it in an OnRep and in the widget’s Construct, so whichever happens last fills it in. That way you don’t care about the order at all.
On the tags
Worth adding to what Auran131 said, Actor Tags are not replicated. You’re AddUnique-ing on the owning client only, so the server and every other client has an empty Tags array on that character. If anything server side ever checks that tag it’ll never find it. A replicated enum or a small struct on the PlayerState is what you want.
One last practical note
If you do keep any RPC flow for something else later, drive it from PossessedBy on the server and OnRep_Controller on the client rather than BeginPlay. Those fire after ownership exists, which is the whole problem here.