Quick answer: A custom UBlueprintAsyncActionBase must broadcast its output delegate to fire the exec pin, and it must stay GC-rooted while pending — RegisterWithGameInstance handles the latter.

A custom “async load and spawn” Blueprint node runs but its “Completed” output pin never executes — the Blueprint just stops there.

Output Pins Are Multicast Delegates

Each output exec pin on an async node corresponds to a BlueprintAssignable multicast delegate. The pin fires only when you Broadcast() that delegate from C++:

UPROPERTY(BlueprintAssignable)
FMyAsyncOutput Completed;

void UMyAsyncAction::OnWorkDone()
{
    Completed.Broadcast(Result);   // THIS fires the exec pin
}

Forget the Broadcast and the node is a dead end.

Keep It Alive

Async actions are UObjects — if nothing roots them, GC collects the action mid-flight and the delegate never broadcasts. In Activate() (or the static factory), call RegisterWithGameInstance so it’s rooted until you call SetReadyToDestroy.

Always Reach an End State

Broadcast a result on every path — success and failure. If the underlying operation can error, have a Failed output and broadcast it. A node with no reachable broadcast on the error path looks “hung”.

Then SetReadyToDestroy

After the final broadcast, call SetReadyToDestroy() so the action unroots and GC can reclaim it. Skip this and you leak one action per call.

Verifying

The async node fires Completed (or Failed) every time. No leaked actions in the object count. The Blueprint continues past the node.

“Async nodes need three things: stay rooted, broadcast on every path, then SetReadyToDestroy.”

Copy the structure of an engine async node (DownloadImage, AsyncLoadAsset) as a template — they get the lifecycle exactly right.