Fix power off (standby) reliability by detecting playactor timeout-kills - #676
Conversation
Standby requires a full Remote Play handshake (discovery, session init,
login, passcode, then the standby request itself) that can exceed the
previous 5s shelljs timeout shared with wake. When shelljs kills the
process on timeout, it exits with code 1 and empty stdout/stderr, so the
`if (stderr) throw stderr` guard in PlayactorClient.runPowerCommand never
fired and the saga optimistically reported STANDBY even though the
console never actually went to standby.
Verified shelljs' timeout-kill behavior locally (sh.exec("sleep N",
{ timeout: shortMs }) consistently yields code: 1, stdout: "",
stderr: "") since this couldn't be reproduced against real PS5 hardware.
- Raise standby's playactor discovery/connect timeouts to 10s each and its
shelljs timeout to 25s, comfortably covering the full handshake. wake's
timeouts are unchanged since it has no equivalent handshake latency.
- Fall back to checking the process exit code when stderr is empty, since
playactor exits 0 on success and non-zero on any other failure - this
catches the timeout-kill case that stderr alone misses, following the
same code-based-check philosophy check() already uses.
- Pass playactor's --ps5 flag for wake/standby when PS4 devices are
excluded, to reduce discovery latency within the timeout budget.
- Add regression tests in client.test.ts covering the timeout-kill path
for both wake and standby, plus the new --ps5 flag behavior.
Fixes FunkeyFlo#675
|
FYI: I dumped over 30 hours into this in the last week. I'm not a programmer, and Claude helped me every step of the way, but I did end up with a very reliable working setup using Playactor independently. Here are the relevant findings summarized by Claude: I'm running playactor directly rather than through the add-on, against a PS5 on system software 13600007, so this is a slightly different vantage point — but it bears on the "not verifiable without hardware" question from the PR. 25s is the right ballpark but isn't always sufficient. I wrapped my standby call in Failed attempts appear to leave stale Remote Play sessions on the console, and those poison subsequent attempts. After a killed attempt I see Practically this means a raised timeout alone may not be enough. What made it reliable for me was retrying with a gap: 25s cap per attempt, up to 4 attempts, ~30s between them. Two consecutive failures followed by success on the third, with nothing else changing, is a pattern I've seen repeatedly. Retrying without a gap seemed to make things worse — presumably stacking up more half-open sessions. One other state worth knowing about: standby reliably hangs at Happy to test 1.7.0 against real hardware and report back if that's useful. |
|
@andrew-codes this change causes standby requests to fail consistently on my system. Rolling back the image to 1.6.0 fixes it. On 1.7.0, I get a notification that a device connected to remote play, and then another disconnected notification a few seconds later; system remains on. Happy to send over logs if you can guide me where to look |
|
Thanks for the report. I'll look into this early this week. Logs would be helpful. Thanks again! |
Fixes #675
The bug
PlayactorClient.runPowerCommand(shared bywake()andstandby()) invokes theplayactorCLI viash.exec(command, { silent: true, timeout: 5000 })and only checksif (stderr) throw stderrto detect failure.Standby requires a full Remote Play handshake (discovery, session init/ctrl, login, passcode, then the actual standby command), which can exceed the shared 5s timeout that's plenty for a fire-and-forget wake. When it does, shelljs kills the process before standby completes. The kill produces no stderr, so the
if (stderr) throw stderrguard never fires, and the saga optimistically reports STANDBY to Home Assistant even though the console never actually went to standby.Credit to the original issue reporter's diagnostic writeup for identifying this shape of the bug and proposing the fix directions below.
What I verified vs. took on faith
sh.exec("sleep N", { silent: true, timeout: shortMs })locally and confirmed it consistently returns{ code: 1, stdout: "", stderr: "" }- no thrown error, no stderr. This confirms the load-bearing claim the whole fix depends on: a timeout-kill is otherwise indistinguishable from success under the old check.check()in the same file already usescode > 1 && stderrrather than trusting stderr alone - but that exact check doesn't transfer towake/standby.check's exit codes 0/1 are two legitimate successful outcomes (Awake/Standby) per playactor's own documented convention (seeExitCode.DeviceStandby = 1in playactor's CLI source), whereaswake/standbyhave no such convention - they exit 0 on success and non-zero on any failure. So the correct check here iscode !== 0, notcode > 1.playactor@0.4.1's CLI genuinely supports a--ps5flag (DeviceOptions.deviceOnlyPS5in its ownoptions.js), confirmed by reading the installed package's source rather than assuming the flag exists.The fix
In
server/src/playactor/client.ts:standby's playactor--timeout/--connect-timeoutfrom 5000ms to 10000ms each, and its shelljs process timeout from 5000ms to 25000ms - comfortably covering discovery + connect + margin for login/passcode/the standby request itself, which have no timeout flag of their own.wake's timeouts are unchanged (5000ms) since it has no equivalent handshake latency - it's a fire-and-forget UDP wake packet, per the issue's own evidence for why wake "just works" while standby doesn't.runPowerCommandnow falls back to checking the exit code when stderr is empty:if (code !== 0) throw .... This catches the timeout-kill case (code 1, no stderr) that the old stderr-only check missed, without changing behavior for the existing stderr-present failure path (still thrown as-is, preserving existing callers/tests that assert on the raw stderr value).--ps5flag towake/standbycommands whenallowPs4Devicesisfalse(mirrors the existinginclude_ps4_devices→allowPs4Devicesconfig flow already used bydiscover-devices.ts), trimming discovery latency within the new timeout budget. ThreadedallowPs4DevicesthroughPlayactorClientSettingsandapp.ts'screatePlayactorClient(...)call to make this available.I scoped this fix to
standby's timeouts only (notwake's) since the reported symptom and the handshake-latency root cause are specific to standby. The exit-code fallback check applies to bothwakeandstandbysince they sharerunPowerCommandand both have the identical latent gap -wakejust doesn't usually hit it in practice, per the issue.Tests
Added to
server/src/playactor/__tests__/client.test.ts:wakeand forstandby: a timeout-kill-shaped result ({ code: 1, stdout: "", stderr: "" }) is rejected, rather than silently resolving.--ps5flag on both commands.standby"builds the correct command" test for the new timeout values.yarn test/unit,yarn typecheck,yarn lint, andyarn format/checkall pass locally, with 100% statement/branch coverage on the touched file.