A 0.25.0 user still cannot connect after the host wakes from sleep — surviving the b6acbd09 probe fix (0.24.0). Their log shows the probe half working exactly as designed and dying in its terminal branch, every session, ~4.6 s per attempt:
no pf-vdisplay device interface found (0 active, 0 inactive)
pf-vdisplay: the adapter devnode exists but could NOT be reloaded … reason=Generic failure
Both reload levers fail: Disable-PnpDevice throws WMI's catch-all, and the pnputil /restart-device fallback exits non-zero — with the old code discarding why on both counts.
The structural cause (commit 3, the headline)
The control-device sharing contract was "bare HANDLE copies, never closed for the process lifetime": retired handles were deliberately kept alive because the pinger/linger threads and the capture delivery closures held raw copies whose soundness depended on no-close. So after a wake left the driver hostless, the host still held open handles to the dead control device — and an open control handle is exactly what vetoes the PnP disable the recovery leans on.reset-pf-vdisplay.ps1 stops the whole host service precisely to get those handles closed; the in-process recovery structurally could not.
Ownership is now Arc<OwnedHandle> all the way out: the manager's accessors hand out clones, every consumer holds its clone across its IOCTLs (the three capture closures each own one — Arc<OwnedHandle> is Send+Sync, ending the raw-isize smuggling), and DeviceSlot::retired is gone. Retiring drops only the manager's reference, so the handle closes when the last in-flight user drains. The recovery releases that reference at the first absent sighting — the existing 3 s ABSENT_SETTLE doubles as the drain window — and again before a not-ready-deadline reload.
Safe because: the driver attaches no meaning to the control file closing (host-gone is the IOCTL-liveness watchdog; EvtFileClose deliberately unhooked), and the dev_raw helper makes every raw-HANDLE view a borrow of the owning Arc, so a concurrent retire can never close a handle mid-IOCTL. Lock-order note: RECOVERY → device is now taken; the forbidden inverse still never occurs (VdisplayDriver::open never reloads).
The two companion fixes the same log demanded
Commit 2 — a refused reload now says WHY, and never targets a phantom. The REFUSED branch reported only the Disable exception and threw away the pnputil exit code (3010 "needs a reboot" is its own diagnosis), the devnode's state, and whether the right devnode was even targeted: Get-PnpDevice lists not-present phantom devnodes (upgrade leftovers), and Select-Object -First 1 could hand every recovery attempt a phantom — whose disable and restart fail exactly like the field log — while a live node sat unexamined. The selector now prefers present (OK-first) nodes, a phantom-only state gets a truthful "the device node is gone; reinstalling re-creates it" refusal, and the REFUSED line carries devnode counts, PnP Status, ConfigManager problem code, and the restart exit code — one field log now decides between handle-veto, phantom, and problem-state.
Commit 1 — the ghost-monitor reap can no longer fail in silence. The reap that keeps departed monitors from exhausting the IddCx slot budget (the 0x80070490 wedge) launched pnputil by bare name — under a LocalSystem PATH that can miss System32, SilentlyContinue swallowed the miss and the host logged nothing: "no ghosts" and "removed nothing" were byte-identical. Now pnputil is resolved via $env:SystemRoot (a SYSTEM process must not trust PATH — a planted pnputil.exe would run elevated), $LASTEXITCODE is pre-seeded per launch, and found/removed are reported unconditionally. Not this reporter's bug (their log refuted it), but the same vacuous-signal family, in the function next door.
Verification
Mac: scripts/xcheck.sh windows clippy (all four Windows-gated crates, --all-targets) + cargo fmt --all --check green.
.133 runner, committed tree (bytes proven on-box first): cargo check -p punktfunk-host --release RC=0; cargo test -p pf-vdisplay --release61 passed / 0 failed / 5 ignored — the first time this suite has run on Windows rather than only compiled.
Still owed: an on-glass wake-cycle test — the close-unblocks-disable claim is argued from the mechanism, not yet observed on hardware. The commit-2 diagnostics confirm or refute it from the reporter's next log either way.
## The field report
A 0.25.0 user still cannot connect after the host wakes from sleep — surviving the `b6acbd09` probe fix (0.24.0). Their log shows the probe half working exactly as designed and dying in its terminal branch, every session, ~4.6 s per attempt:
```
no pf-vdisplay device interface found (0 active, 0 inactive)
pf-vdisplay: the adapter devnode exists but could NOT be reloaded … reason=Generic failure
```
Both reload levers fail: `Disable-PnpDevice` throws WMI's catch-all, and the `pnputil /restart-device` fallback exits non-zero — with the old code discarding *why* on both counts.
## The structural cause (commit 3, the headline)
The control-device sharing contract was "bare `HANDLE` copies, never closed for the process lifetime": retired handles were deliberately kept alive because the pinger/linger threads and the capture delivery closures held raw copies whose soundness depended on no-close. So after a wake left the driver hostless, the host still held open handles to the dead control device — **and an open control handle is exactly what vetoes the PnP disable the recovery leans on.** `reset-pf-vdisplay.ps1` stops the whole host service precisely to get those handles closed; the in-process recovery structurally could not.
Ownership is now `Arc<OwnedHandle>` all the way out: the manager's accessors hand out clones, every consumer holds its clone across its IOCTLs (the three capture closures each own one — `Arc<OwnedHandle>` is `Send+Sync`, ending the raw-`isize` smuggling), and `DeviceSlot::retired` is gone. Retiring drops only the manager's reference, so the handle **closes when the last in-flight user drains**. The recovery releases that reference at the first absent sighting — the existing 3 s `ABSENT_SETTLE` doubles as the drain window — and again before a not-ready-deadline reload.
Safe because: the driver attaches no meaning to the control file closing (host-gone is the IOCTL-liveness watchdog; `EvtFileClose` deliberately unhooked), and the `dev_raw` helper makes every raw-`HANDLE` view a borrow of the owning Arc, so a concurrent retire can never close a handle mid-IOCTL. Lock-order note: `RECOVERY → device` is now taken; the forbidden inverse still never occurs (`VdisplayDriver::open` never reloads).
## The two companion fixes the same log demanded
**Commit 2 — a refused reload now says WHY, and never targets a phantom.** The `REFUSED` branch reported only the Disable exception and threw away the pnputil exit code (3010 "needs a reboot" is its own diagnosis), the devnode's state, and whether the right devnode was even targeted: `Get-PnpDevice` lists not-present phantom devnodes (upgrade leftovers), and `Select-Object -First 1` could hand every recovery attempt a phantom — whose disable *and* restart fail exactly like the field log — while a live node sat unexamined. The selector now prefers present (OK-first) nodes, a phantom-only state gets a truthful "the device node is gone; reinstalling re-creates it" refusal, and the `REFUSED` line carries devnode counts, PnP `Status`, ConfigManager problem code, and the restart exit code — one field log now decides between handle-veto, phantom, and problem-state.
**Commit 1 — the ghost-monitor reap can no longer fail in silence.** The reap that keeps departed monitors from exhausting the IddCx slot budget (the `0x80070490` wedge) launched pnputil by bare name — under a LocalSystem PATH that can miss System32, `SilentlyContinue` swallowed the miss and the host logged nothing: "no ghosts" and "removed nothing" were byte-identical. Now pnputil is resolved via `$env:SystemRoot` (a SYSTEM process must not trust PATH — a planted `pnputil.exe` would run elevated), `$LASTEXITCODE` is pre-seeded per launch, and found/removed are reported unconditionally. Not this reporter's bug (their log refuted it), but the same vacuous-signal family, in the function next door.
## Verification
- Mac: `scripts/xcheck.sh windows clippy` (all four Windows-gated crates, `--all-targets`) + `cargo fmt --all --check` green.
- `.133` runner, committed tree (bytes proven on-box first): `cargo check -p punktfunk-host --release` RC=0; `cargo test -p pf-vdisplay --release` **61 passed / 0 failed / 5 ignored** — the first time this suite has *run* on Windows rather than only compiled.
- Still owed: an on-glass wake-cycle test — the close-unblocks-disable claim is argued from the mechanism, not yet observed on hardware. The commit-2 diagnostics confirm or refute it from the reporter's next log either way.
The reap that keeps departed virtual monitors from exhausting the IddCx
monitor-slot budget launched pnputil by BARE NAME — under the LocalSystem
service's PATH that can miss System32, SilentlyContinue swallowed the
miss, and the Rust side logged only when the count was positive: a reap
that removed nothing and a box with no ghosts were byte-identical
(silence). Ghosts then ratcheted up with every sleep cycle until
IOCTL_ADD wedged at 0x80070490 and every session black-screened — and
the wedge self-heal shipped in 0.25.0 retried an ADD behind a reap that
could never remove anything, which is exactly a persistent post-sleep
"no connection" surviving the b6acbd09 probe fix.
Same family and same cure as the adapter-reload path one function down:
resolve pnputil via $env:SystemRoot (a SYSTEM process must not trust
PATH anyway — a planted pnputil.exe would run elevated), pre-seed
$LASTEXITCODE to failure before every launch, and report found AND
removed unconditionally so "no ghosts" and "removed nothing" are
finally distinguishable in a field log. The report parse is split out
and pinned by tests like classify_reload_output.
Field log 2026-08-08 (0.25.0, wake from sleep): every session died on
'the adapter devnode could not be reloaded (Generic failure)' — the WMI
catch-all — because the REFUSED branch reported only the Disable
exception and threw away everything that would identify the failure
mode: why the pnputil /restart-device fallback ALSO failed (its exit
code — 3010 'needs a reboot' is its own diagnosis), what state the
devnode was in, and whether the right devnode was even targeted.
That last one is a real trap, not just missing telemetry: Get-PnpDevice
lists not-present PHANTOM devnodes (upgrade/reinstall leftovers), and
Select-Object -First 1 could hand every recovery attempt a phantom —
whose disable and restart both fail exactly like the field log — while
a live node sat unexamined. The selector now prefers present nodes (OK
before problem-state), and a phantom-only state gets a truthful
refusal: no reload can revive a devnode record whose device is gone;
only reinstalling re-creates it.
The REFUSED line now carries devnode counts, the chosen node's PnP
Status + ConfigManager problem code, and the restart exit code, so the
next field log decides between handle-veto, phantom, and problem-state
instead of reading 'Generic failure'. Decode pinned by test.
The control-device sharing contract was 'bare HANDLE copies, never
closed for the process lifetime': retired handles were deliberately kept
alive because the pinger/linger threads and the capture delivery
closures held raw copies whose soundness depended on no-close. The cost
surfaced in the 2026-08-08 field log: after a wake left the driver
hostless, every adapter reload came back REFUSED (Generic failure) —
and an open control handle is exactly what vetoes the PnP disable (and
can wedge the pnputil restart) the recovery leans on.
reset-pf-vdisplay.ps1 stops the whole host service precisely to get
those handles closed; the in-process recovery could not, because the
process could never close them.
Ownership is now Arc all the way out: ensure_device/device_handle/
control_device_handle hand out Arc<OwnedHandle> clones, every consumer
holds its clone across its IOCTLs (the capture closures each own one —
Arc<OwnedHandle> is Send+Sync, ending the isize smuggling), and
retiring drops only the manager's reference, so the handle CLOSES when
the last in-flight user drains. DeviceSlot::retired is gone. The
recovery path now releases the manager's reference at the first absent
sighting — the 3 s ABSENT_SETTLE doubles as the drain window — and
again before a not-ready-deadline reload, so the PnP cycle finally runs
against a device the host is no longer holding open.
The driver attaches no meaning to the control file closing (host-gone
is the IOCTL-liveness watchdog, EvtFileClose deliberately unhooked), so
the close has no driver-side side effects. Lock order note: RECOVERY →
device is now taken (the release hooks); the forbidden inverse still
never occurs — VdisplayDriver::open never reloads.
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.
The field report
A 0.25.0 user still cannot connect after the host wakes from sleep — surviving the
b6acbd09probe fix (0.24.0). Their log shows the probe half working exactly as designed and dying in its terminal branch, every session, ~4.6 s per attempt:Both reload levers fail:
Disable-PnpDevicethrows WMI's catch-all, and thepnputil /restart-devicefallback exits non-zero — with the old code discarding why on both counts.The structural cause (commit 3, the headline)
The control-device sharing contract was "bare
HANDLEcopies, never closed for the process lifetime": retired handles were deliberately kept alive because the pinger/linger threads and the capture delivery closures held raw copies whose soundness depended on no-close. So after a wake left the driver hostless, the host still held open handles to the dead control device — and an open control handle is exactly what vetoes the PnP disable the recovery leans on.reset-pf-vdisplay.ps1stops the whole host service precisely to get those handles closed; the in-process recovery structurally could not.Ownership is now
Arc<OwnedHandle>all the way out: the manager's accessors hand out clones, every consumer holds its clone across its IOCTLs (the three capture closures each own one —Arc<OwnedHandle>isSend+Sync, ending the raw-isizesmuggling), andDeviceSlot::retiredis gone. Retiring drops only the manager's reference, so the handle closes when the last in-flight user drains. The recovery releases that reference at the first absent sighting — the existing 3 sABSENT_SETTLEdoubles as the drain window — and again before a not-ready-deadline reload.Safe because: the driver attaches no meaning to the control file closing (host-gone is the IOCTL-liveness watchdog;
EvtFileClosedeliberately unhooked), and thedev_rawhelper makes every raw-HANDLEview a borrow of the owning Arc, so a concurrent retire can never close a handle mid-IOCTL. Lock-order note:RECOVERY → deviceis now taken; the forbidden inverse still never occurs (VdisplayDriver::opennever reloads).The two companion fixes the same log demanded
Commit 2 — a refused reload now says WHY, and never targets a phantom. The
REFUSEDbranch reported only the Disable exception and threw away the pnputil exit code (3010 "needs a reboot" is its own diagnosis), the devnode's state, and whether the right devnode was even targeted:Get-PnpDevicelists not-present phantom devnodes (upgrade leftovers), andSelect-Object -First 1could hand every recovery attempt a phantom — whose disable and restart fail exactly like the field log — while a live node sat unexamined. The selector now prefers present (OK-first) nodes, a phantom-only state gets a truthful "the device node is gone; reinstalling re-creates it" refusal, and theREFUSEDline carries devnode counts, PnPStatus, ConfigManager problem code, and the restart exit code — one field log now decides between handle-veto, phantom, and problem-state.Commit 1 — the ghost-monitor reap can no longer fail in silence. The reap that keeps departed monitors from exhausting the IddCx slot budget (the
0x80070490wedge) launched pnputil by bare name — under a LocalSystem PATH that can miss System32,SilentlyContinueswallowed the miss and the host logged nothing: "no ghosts" and "removed nothing" were byte-identical. Now pnputil is resolved via$env:SystemRoot(a SYSTEM process must not trust PATH — a plantedpnputil.exewould run elevated),$LASTEXITCODEis pre-seeded per launch, and found/removed are reported unconditionally. Not this reporter's bug (their log refuted it), but the same vacuous-signal family, in the function next door.Verification
scripts/xcheck.sh windows clippy(all four Windows-gated crates,--all-targets) +cargo fmt --all --checkgreen..133runner, committed tree (bytes proven on-box first):cargo check -p punktfunk-host --releaseRC=0;cargo test -p pf-vdisplay --release61 passed / 0 failed / 5 ignored — the first time this suite has run on Windows rather than only compiled.