Quick answer: Move the event a few frames before the clip end — the exact last frame can be skipped when the state transitions out. For end-of-clip logic, use a state behaviour’s OnStateExit.

An attack clip has an Animation Event on its final frame to re-enable input. On non-looping clips it sometimes never fires — the state transitioned away before the last frame sampled.

Why the Last Frame Is Unreliable

When a non-looping state finishes, the Animator may transition out before sampling the exact final frame. An event sitting on frame N (the last) can be skipped; an event a few frames earlier always fires.

Move the Event Earlier

Place the event ~2–5 frames before the end. The animation visually finishes the same; the event reliably triggers. For “re-enable input”, slightly early is usually fine anyway.

Use a StateMachineBehaviour

public class AttackEndBehaviour : StateMachineBehaviour
{
    public override void OnStateExit(Animator a, AnimatorStateInfo s, int layer)
    {
        a.GetComponent<PlayerInput>()?.Enable();
    }
}

OnStateExit fires reliably whenever the state ends — including via interruption. Best for “always run this when the attack stops” logic.

Loop-Clip Caveat

On looping clips, an event exactly at the loop point can double-fire or skip depending on wrap. Same fix: nudge it slightly off the boundary.

Verifying

Play the attack to completion many times — the end event (or OnStateExit) fires every time. Interrupting the attack still re-enables input via OnStateExit.

“The exact last frame is unreliable. Nudge events inward, or use OnStateExit for end-of-state logic.”

OnStateExit is the robust choice for ‘cleanup when this animation stops’ — it survives interruptions that an end-frame event never sees.