Correct way to make a tree of actors wrt. GC?

In my game, I want to have a set of instructions collected into scripts held by a script runner (script data + instruction pointer and context) bundled into a script manager and finally referenced by the game mode. I decided to make them actors so that they could be inspected, customized in blueprints, spawned, and debugged. This seems to work great, except when I want to clean up scripts.

In my script manager, I have this:

Runner->Destroy();

and I’ve verified that both the script runners and script themselves are being cleaned up effectively. but not the instructions. That surprises me because the scripts seem to be cleaned up just fine with this code:

  AScriptRunner::~AScriptRunner() {
    if (Script) Script->Destroy();
  }

  // Not sure if this is needed, but oh well
  void AScriptRunner::EndPlay(const EEndPlayReason::Type EndPlayReason) {
    Super::EndPlay(EndPlayReason);
    if (Script) Script->Destroy();
  }

Yet doing the same in the script doesn’t work.

AInScript::~AInScript() {
  for (AInstruction* I : Instructions) if (I) I->Destroy(); // (1)
}

void AInScript::EndPlay(const EEndPlayReason::Type EndPlayReason) {
  Super::EndPlay(EndPlayReason);
  for (AInstruction* I : Instructions) if (I) I->Destroy();
}

The instructions can clearly be seen in the editor even after the script no longer exists, until I end the session. When I do end the session, it often crashes on line (1).

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION 0x0000000000000000

And that’s despite checking for nullptr! At first I thought maybe this is due a race condition with parallel GC (Check I isn’t null passes, GC runs and nulls it, then we try I->Destroy()), but I disabled it

and it still happens.

I read about enabling GC clusters, overriding CanBeInCluster() for both instructions and scripts, and AActor::AddToCluster(passing the script which the instruction belongs to), though information about this method seemed very slim and it affected no change.

One idea that is on my mind - Destroy and End play are two different logic branches to end Actor lifecycle. So, you can add one more override of this

virtual void BeginDestroy();

That, or something I did last night, actually seemed to do the trick.