the console home's can_wake: !online && !h.mac.is_empty()
punktfunk wake, which exits 5 when the record has no MAC
That MAC only ever reached the store through trust::learn_mac, fed from the host's mDNS mac TXT — and learn_mac had exactly two callers: the GTK hosts page and the WinUI one.
Neither runs on a Steam Deck. Gaming Mode has only the Decky panel (which drives the headless CLI: discover, hosts list, hosts add, launch) and the console home — and both of those learned the management port alone, never the MAC. So a Deck's records stayed MAC-less forever, every wake gate stayed false, and Wake-on-LAN was skipped in silence: no packet, no error, nothing to show a user. It worked on desktop purely because those two hosts pages learn on every discovery tick.
Ruled out along the way: the magic-packet sender is fine (punktfunk-core::wol, 7/7 tests — per-interface bind, subnet-directed + limited broadcast, unicast, ports 9 and 7), and so is the flatpak sandbox (--share=network). Nothing ever reached them.
The change
Rather than add the missing call twice, the three per-field learners — learn_mac, learn_os, learn_mgmt_port, three pub fns doing three separate load/save cycles — collapse into one trust::learn_from_advert, called at every site where an advert meets a saved record:
Site
Before
After
clients/linux/src/ui_hosts.rs (GTK)
3 advert scans, 3 calls
1
clients/windows/src/app/hosts.rs (WinUI)
3 advert scans, 3 calls
1
clients/session/src/console.rs (console home)
mgmt port only
1
clients/cli/src/main.rs (discover)
nothing
1
Remembering one call is not something a front-end can half-do; remembering three is precisely what produced this bug. It takes the three fields rather than a DiscoveredHost because there are two of those — core's and the WinUI shell's verbatim port — and one function has to serve both.
discover is where the panel-only flow is fixed, since it is the one verb the Decky panel runs that ever sees an advert. It keeps KnownHosts::read(), so it still mints no ids and cannot join the race that comment warns about, and learn_from_advert writes only when an advert genuinely taught the record something — a steady-state panel refresh touches no disk.
Falling out of the same root cause
The console home now persists the OS chain too, so a Deck host's icon stops vanishing the moment mDNS goes quiet (it was reading the advert with a fallback to h.os, which on a Deck was always empty).
punktfunk wake's "connect to it once while it's awake so the client can learn it" is replaced. A MAC comes from an advert and never from a connect — that wording is what sent this diagnosis looking in the wrong place.
Verification
cargo clippy -p pf-client-core -p punktfunk-cli -p punktfunk-client-session -p punktfunk-client-linux --all-targets --locked -- -D warnings — clean, in the punktfunk-rust-ci image (macOS compiles none of this code).
cargo test -p pf-client-core --lib trust:: — 25/25, including the new apply_advert_learns_what_it_carries_and_keeps_what_it_omits: what an advert carries lands, a repeat reports no change (the no-disk-write property every per-tick caller depends on), and — the one that would actually cost a user their wake — a field the advert omits never clears a learned one.
cargo fmt --all --check — clean.
Not verified locally:clients/windows needs an MSVC target. Its change is mechanically identical to the GTK one and the re-export list in clients/windows/src/trust.rs is updated, but it rides on CI's Windows leg — worth a look at that job.
Known limit, left alone deliberately: a Deck learns the MAC on the next discovery after saving a host, not at save time. That is inherent — a sleeping host does not advertise — and matches desktop behaviour, so no hosts add --mac path was added for it.
Fixes #322 — Steam Deck Wake-on-LAN does not work.
## Root cause
Every wake gate in the codebase reads `!host.mac.is_empty()` against the **saved record**:
- `ConnectPlan::for_host` / `for_target` — `wake: settings.auto_wake && !host.mac.is_empty()`
- the console home's `can_wake: !online && !h.mac.is_empty()`
- `punktfunk wake`, which exits 5 when the record has no MAC
That MAC only ever reached the store through `trust::learn_mac`, fed from the host's mDNS `mac` TXT — and `learn_mac` had exactly **two** callers: the GTK hosts page and the WinUI one.
Neither runs on a Steam Deck. Gaming Mode has only the Decky panel (which drives the headless CLI: `discover`, `hosts list`, `hosts add`, `launch`) and the console home — and both of those learned the *management port* alone, never the MAC. So a Deck's records stayed MAC-less forever, every wake gate stayed false, and Wake-on-LAN was skipped in silence: no packet, no error, nothing to show a user. It worked on desktop purely because those two hosts pages learn on every discovery tick.
Ruled out along the way: the magic-packet sender is fine (`punktfunk-core::wol`, 7/7 tests — per-interface bind, subnet-directed + limited broadcast, unicast, ports 9 and 7), and so is the flatpak sandbox (`--share=network`). Nothing ever reached them.
## The change
Rather than add the missing call twice, the three per-field learners — `learn_mac`, `learn_os`, `learn_mgmt_port`, three `pub fn`s doing three separate load/save cycles — collapse into one `trust::learn_from_advert`, called at **every** site where an advert meets a saved record:
| Site | Before | After |
|---|---|---|
| `clients/linux/src/ui_hosts.rs` (GTK) | 3 advert scans, 3 calls | 1 |
| `clients/windows/src/app/hosts.rs` (WinUI) | 3 advert scans, 3 calls | 1 |
| `clients/session/src/console.rs` (console home) | mgmt port only | 1 |
| `clients/cli/src/main.rs` (`discover`) | nothing | 1 |
Remembering one call is not something a front-end can half-do; remembering three is precisely what produced this bug. It takes the three fields rather than a `DiscoveredHost` because there are two of those — core's and the WinUI shell's verbatim port — and one function has to serve both.
**`discover` is where the panel-only flow is fixed**, since it is the one verb the Decky panel runs that ever sees an advert. It keeps `KnownHosts::read()`, so it still mints no ids and cannot join the race that comment warns about, and `learn_from_advert` writes only when an advert genuinely taught the record something — a steady-state panel refresh touches no disk.
## Falling out of the same root cause
- The console home now persists the **OS chain** too, so a Deck host's icon stops vanishing the moment mDNS goes quiet (it was reading the advert with a fallback to `h.os`, which on a Deck was always empty).
- `punktfunk wake`'s *"connect to it once while it's awake so the client can learn it"* is replaced. A MAC comes from an advert and never from a connect — that wording is what sent this diagnosis looking in the wrong place.
## Verification
- `cargo clippy -p pf-client-core -p punktfunk-cli -p punktfunk-client-session -p punktfunk-client-linux --all-targets --locked -- -D warnings` — clean, in the `punktfunk-rust-ci` image (macOS compiles none of this code).
- `cargo test -p pf-client-core --lib trust::` — 25/25, including the new `apply_advert_learns_what_it_carries_and_keeps_what_it_omits`: what an advert carries lands, a repeat reports no change (the no-disk-write property every per-tick caller depends on), and — the one that would actually cost a user their wake — a field the advert omits never clears a learned one.
- `cargo fmt --all --check` — clean.
**Not verified locally:** `clients/windows` needs an MSVC target. Its change is mechanically identical to the GTK one and the re-export list in `clients/windows/src/trust.rs` is updated, but it rides on CI's Windows leg — worth a look at that job.
**Known limit, left alone deliberately:** a Deck learns the MAC on the *next* discovery after saving a host, not at save time. That is inherent — a sleeping host does not advertise — and matches desktop behaviour, so no `hosts add --mac` path was added for it.
Every wake gate in the codebase reads `!host.mac.is_empty()` against the saved
record — `ConnectPlan::wake`, the console's `can_wake`, `punktfunk wake`. That MAC
only ever reached the store through `trust::learn_mac`, and `learn_mac` had exactly
two callers: the GTK hosts page and the WinUI one.
Neither runs on a Steam Deck. Gaming Mode has only the Decky panel (which drives the
headless CLI) and the console home — and those learned the management port alone,
never the MAC. So a Deck's records stayed MAC-less forever, every wake gate stayed
false, and Wake-on-LAN was skipped silently: no packet, no error, nothing to see.
It worked on desktop purely because those two hosts pages learn on each discovery
tick. (#322)
Rather than add the missing call twice, collapse the three per-field learners
(`learn_mac`, `learn_os`, `learn_mgmt_port` — three `pub fn`s, three load/save
cycles) into one `learn_from_advert`, and call it at every site where an advert
meets a saved record: both desktop hosts pages, the console home, and the CLI's
`discover`. Remembering one call is not a thing a front-end can half-do; remembering
three is what produced this. It takes the three fields rather than a `DiscoveredHost`
because there are two of those — core's and the WinUI shell's verbatim port.
`discover` is where the panel-only flow is fixed: it is the one verb the Decky panel
runs that ever sees an advert. It keeps `KnownHosts::read()`, so it still mints no
ids and cannot join the race that comment warns about, and `learn_from_advert` writes
only when an advert genuinely taught the record something — a steady-state panel
refresh touches no disk.
Two things fall out of the same root cause: the console home now persists the OS
chain too, so a Deck host's icon stops vanishing the moment mDNS goes quiet; and
`punktfunk wake`'s "connect to it once while it's awake" is replaced, since a MAC
comes from an advert and never from a connect — that wording sent this diagnosis
looking in the wrong place.
The magic-packet sender itself was never at fault (`punktfunk-core::wol` passes its
7 tests) and neither was the flatpak sandbox (`--share=network`). Nothing reached
them.
Closes#322
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #322 — Steam Deck Wake-on-LAN does not work.
Root cause
Every wake gate in the codebase reads
!host.mac.is_empty()against the saved record:ConnectPlan::for_host/for_target—wake: settings.auto_wake && !host.mac.is_empty()can_wake: !online && !h.mac.is_empty()punktfunk wake, which exits 5 when the record has no MACThat MAC only ever reached the store through
trust::learn_mac, fed from the host's mDNSmacTXT — andlearn_machad exactly two callers: the GTK hosts page and the WinUI one.Neither runs on a Steam Deck. Gaming Mode has only the Decky panel (which drives the headless CLI:
discover,hosts list,hosts add,launch) and the console home — and both of those learned the management port alone, never the MAC. So a Deck's records stayed MAC-less forever, every wake gate stayed false, and Wake-on-LAN was skipped in silence: no packet, no error, nothing to show a user. It worked on desktop purely because those two hosts pages learn on every discovery tick.Ruled out along the way: the magic-packet sender is fine (
punktfunk-core::wol, 7/7 tests — per-interface bind, subnet-directed + limited broadcast, unicast, ports 9 and 7), and so is the flatpak sandbox (--share=network). Nothing ever reached them.The change
Rather than add the missing call twice, the three per-field learners —
learn_mac,learn_os,learn_mgmt_port, threepub fns doing three separate load/save cycles — collapse into onetrust::learn_from_advert, called at every site where an advert meets a saved record:clients/linux/src/ui_hosts.rs(GTK)clients/windows/src/app/hosts.rs(WinUI)clients/session/src/console.rs(console home)clients/cli/src/main.rs(discover)Remembering one call is not something a front-end can half-do; remembering three is precisely what produced this bug. It takes the three fields rather than a
DiscoveredHostbecause there are two of those — core's and the WinUI shell's verbatim port — and one function has to serve both.discoveris where the panel-only flow is fixed, since it is the one verb the Decky panel runs that ever sees an advert. It keepsKnownHosts::read(), so it still mints no ids and cannot join the race that comment warns about, andlearn_from_advertwrites only when an advert genuinely taught the record something — a steady-state panel refresh touches no disk.Falling out of the same root cause
h.os, which on a Deck was always empty).punktfunk wake's "connect to it once while it's awake so the client can learn it" is replaced. A MAC comes from an advert and never from a connect — that wording is what sent this diagnosis looking in the wrong place.Verification
cargo clippy -p pf-client-core -p punktfunk-cli -p punktfunk-client-session -p punktfunk-client-linux --all-targets --locked -- -D warnings— clean, in thepunktfunk-rust-ciimage (macOS compiles none of this code).cargo test -p pf-client-core --lib trust::— 25/25, including the newapply_advert_learns_what_it_carries_and_keeps_what_it_omits: what an advert carries lands, a repeat reports no change (the no-disk-write property every per-tick caller depends on), and — the one that would actually cost a user their wake — a field the advert omits never clears a learned one.cargo fmt --all --check— clean.Not verified locally:
clients/windowsneeds an MSVC target. Its change is mechanically identical to the GTK one and the re-export list inclients/windows/src/trust.rsis updated, but it rides on CI's Windows leg — worth a look at that job.Known limit, left alone deliberately: a Deck learns the MAC on the next discovery after saving a host, not at save time. That is inherent — a sleeping host does not advertise — and matches desktop behaviour, so no
hosts add --macpath was added for it.