Quick answer: Store the FSmartObjectClaimHandle and call Release on every exit path — behavior complete, aborted, AI killed, level streamed out.

AI uses Smart Objects for “use bench” behaviors. Over time, benches stop being usable — AI that claimed slots died or aborted without releasing them.

Claim/Use/Release Lifecycle

FSmartObjectClaimHandle ClaimHandle =
    SmartObjectSubsystem->MarkSlotAsClaimed(SlotHandle, ...);

// ... AI walks to and uses the slot ...

SmartObjectSubsystem->MarkSlotAsFree(ClaimHandle);   // MUST happen

A claimed slot is unavailable to others until freed. Miss the free and the slot leaks.

Release on Every Exit Path

Behaviors abort for many reasons. Release in all of them:

In a State Tree task, release in ExitState — it runs for both success and abort.

Owner-Tracked Cleanup

Bind to the AI’s OnDestroyed / OnEndPlay to release any held claim as a safety net:

Pawn->OnEndPlay.AddDynamic(this, &UMyTask::ReleaseClaimOnDeath);

Verifying

Run AI through use cycles, killing some mid-use. Slots free up correctly. smartobject.debug console shows no permanently-claimed leaked slots.

“Claim and free must be balanced on every code path. ExitState plus an OnDestroyed safety net covers it.”

Audit with the Smart Object debugger periodically — a slowly-growing count of claimed slots is the early warning sign of a leak.