Quick answer: Refresh the controller handle each frame via GetConnectedControllers. Action set handles are stable; controller handles may change. Call RunFrame before activation.

A game switches from Gameplay to UI action set on menu open. SteamInput::ActivateActionSet returns without error but bindings don’t change. Controller handle was stale.

Refresh Controller Handle

InputHandle_t handles[STEAM_INPUT_MAX_COUNT];
int count = SteamInput()->GetConnectedControllers(handles);

for (int i = 0; i < count; i++) {
    SteamInput()->ActivateActionSet(handles[i], uiActionSetHandle);
}

Don’t cache handles across frames. Refresh per call.

Get Action Set Handles Once

InputActionSetHandle_t uiActionSet = SteamInput()->GetActionSetHandle("UIControls");
InputActionSetHandle_t gameplayActionSet = SteamInput()->GetActionSetHandle("GameplayControls");

Action set handles are stable; obtain once at startup.

RunFrame Before Activation

SteamInput()->RunFrame();   // pump events
// now safe to ActivateActionSet, GetDigitalActionData, etc.

Call RunFrame every game frame before reading or writing Steam Input.

Verify ActionSet in Steam Overlay

While playing, Shift+Tab → Steam overlay → Controller Configuration. Live binding display shows current action set. Confirms whether your code switched correctly.

Verifying

Open menu; UI bindings active. Close menu; gameplay bindings active. No stale frames where wrong action set is read.

“Refresh controller handles per frame; action sets are stable. RunFrame ties it together.”

For Steam Deck, the Deck’s back paddles map per action set — design two sets so users can configure paddles differently in menus vs gameplay.