Skip to content

fix(input-plumber): log + retry the boot-time wake-button reload#206

Merged
srsholmes merged 2 commits into
mainfrom
claude/wake-reload-logging
Jul 8, 2026
Merged

fix(input-plumber): log + retry the boot-time wake-button reload#206
srsholmes merged 2 commits into
mainfrom
claude/wake-reload-logging

Conversation

@srsholmes

Copy link
Copy Markdown
Owner

Problem

The overlay wake button — a physical button bound through InputPlumber (e.g. the handheld's Keyboard button → F16) — is re-applied on every boot by reloadPersistedProfile(). When that reload fails, the button silently reverts to its OS default (opening the on-screen keyboard), and there were almost no diagnostics to explain why:

  • reloadPersistedProfile() had no internal logging.
  • onLoad only logged on failure, and did a single attempt.

Two real failure modes hit this:

  1. Stale saved capability after an InputPlumber update (observed: SteamOS 3.9 / build 20260630 bumped IP to 0.77.7). The old saved capability string no longer matched the device, so the reload restored nothing. Re-capturing the button fixes it — but that's invisible without logs.
  2. Cold-boot race: IP is up (so the internal waitForIp passes) but the composite device isn't enumerated yet → reloadPersistedProfile() returns "Bound device not connected" and the binding is dropped for the whole session.

Changes

  • Logging in reloadPersistedProfile() (non-Deck path): binding read, IP-up + device list, whether the saved capability is still present in the device's capabilities (catches post-update renames), and the LoadProfilePath result. Also log when a binding is persisted at bind time.
  • Retry in onLoad: reload is retried up to 5× (2s apart), mirroring the existing restartInputPlumber() retry, so the cold-boot enumeration race can't silently drop the binding. No-op when nothing is bound (early ok return).

Diagnose

journalctl -u loadout.service -b --no-pager | grep "wake reload"

Verification

Built and installed on a OneXPlayer APEX (non-Deck). A real cold reboot logs the full happy path and restores the binding:

wake reload: restoring binding raw="Gamepad:Button:Keyboard" device="ONEXPLAYER APEX"
wake reload: IP up, 1 composite device(s): [ONEXPLAYER APEX]
wake reload: applying to device "ONEXPLAYER APEX" … saved cap "Gamepad:Button:Keyboard" present
wake reload: LoadProfilePath OK — wake button "Gamepad:Button:Keyboard" restored

Typecheck clean; input-plumber lib tests pass (67). The app.spec.tsx render tests were already failing on main (pre-existing, unrelated).

🤖 Generated with Claude Code

The overlay wake button (a physical button bound through InputPlumber, e.g.
the handheld's Keyboard button → F16) is re-applied on boot by
reloadPersistedProfile(). When that reload failed — a stale saved capability
after an InputPlumber update, or the composite device not yet enumerated on a
cold boot — the button silently reverted to its OS default (opening the
on-screen keyboard) with almost no diagnostics: reloadPersistedProfile() had
no internal logging and onLoad only warned on failure.

- Add step-by-step logging to reloadPersistedProfile(): binding read, IP-up,
  composite devices found, whether the saved capability is still present in
  the device (catches post-update capability renames), and LoadProfilePath
  result. Log when a binding is persisted, too.
- onLoad now retries the reload up to 5× (2s apart), mirroring
  restartInputPlumber(), so a cold boot where IP is up but the pad isn't
  enumerated yet doesn't drop the binding for the whole session. No-op when
  nothing is bound.

Diagnose a bad boot with:
  journalctl -u loadout.service -b --no-pager | grep "wake reload"

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@srsholmes srsholmes force-pushed the claude/wake-reload-logging branch from a5c30c1 to 81dbb89 Compare July 8, 2026 14:18
The retry loop lived inline in backend.onLoad, where it was awkward to test
(real 2s sleeps + onLoad's other side effects). Extract it into
`reloadPersistedProfileWithRetry()` in wake-trigger.ts with injectable
`reload` + `wait`, and point onLoad at it (behavior unchanged).

Add unit tests: success first try (no wait), no-op when nothing is bound,
retry-then-succeed (asserts call count, waits, and onRetry attempts),
give-up after `attempts` with no trailing wait, and a custom attempts count.
Runs in ~80ms — no real delays.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@srsholmes srsholmes merged commit 6dda9f1 into main Jul 8, 2026
2 checks passed
@srsholmes srsholmes deleted the claude/wake-reload-logging branch July 8, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant