Three things a Discord field report (Windows host, 0.1x → canary) turned up. Two are fixed here; the third is diagnosed in the notes below but not fixed.
1. "It thinks I have Sunshine/Apollo running" — it didn't
Both were uninstalled. Sunshine's and Apollo's uninstallers leave their config/ and logs/ directories in Program Files behind, and detect.rs counted a bare directory — or a service registered at any start type, including disabled — as a live conflict.
The installer's own probe was narrowed to "service start type <= 2" after exactly this aborted a winget install in the field, and the tray dropped its always-on warning for the same reason in 3e782852. The runtime probe never got the same treatment — so the one surface a user actually looks at kept shouting, and reinstalling Punktfunk could never clear it, because the leftover is not our state.
Evidence::is_active now draws the line (running, or set to start on its own). Only active detections reach the startup warning, the detect-conflicts exit code, and /local/summary. Dormant findings still appear in the full report, under a heading saying they need no action — that report is where "why does it think I have Apollo?" gets answered.
Windows reads the real SCM start type via query_config; unreadable ⇒ reported dormant (a genuinely running host is caught by the process scan anyway).
Linux checks for a .wants/.requiressymlink, not merely a unit file — an installed-but-not-enabled unit starts nothing.
detect-conflicts now exits 1 only for an active conflict, so installers/support scripts gating on it stop aborting over leftovers.
2. The console card claimed "running" regardless of evidence
host_conflicts_title was hardcoded to "Another game-streaming server is running on this machine", so a leftover folder was announced as a running server. It now says "active", and each entry names what was actually observed — Sunshine (running), Apollo (starts automatically).
3. "The exclusive screen never times out re-enabling the display"
restore_displays_ccd runs from exactly one place — teardown_removed, gated on the in-memoryccd_saved. Windows deliberately never saves the isolated topology to the CCD database, precisely so teardown can restore the user's layout. So a host that crashed, was killed, or was stopped mid-session left the operator's panels deactivated with nothing in the product to put them back.
There was already a startup recovery leg, but only for the experimentalpnp_disable_monitors axis, which is false by default — the default Exclusive path had no recovery at all.
New isolate_journal:
marks the deactivated targets before the apply (dying mid-apply is inside the window this exists to cover),
clears after the restore — restore_displays_ccd wraps an _inner so a crash part-way through keeps the marker,
startup_recover() force-EXTENDs at host start if a marker survived.
EXTEND rather than replaying the saved CCD blob on purpose: that blob pins the virtual display's target id, which dies with the crashed host, so a replay would mostly fail ERROR_BAD_CONFIGURATION into the very backstop restore_displays_ccd already keeps — and EXTEND stays correct across a reboot, where saved ids are stale anyway.
Verification
Green on three boxes, each with an on-box proof that the machine compiled these bytes (not a stale tree):
Linux .21
Windows .133
Windows .173
cargo fmt --all --check
0
0
0
clippy -D warnings
0 (--workspace --all-targets --locked)
0
0
detect tests
15/15
15/15
15/15
isolate_journal tests
— (Windows-only)
—
4/4
isolate_journal carries 4 unit tests: mark→pending()→clear round-trip, empty set writes no marker, an unchanged re-mark does not rewrite (asserted with a sentinel rather than mtimes, which would pass vacuously at coarse filesystem timestamp resolution), and a corrupt marker still requests recovery. They are possible because pf_paths::config_dir() honors PUNKTFUNK_CONFIG_DIR, and because startup_recover was split into pending() (the decision) plus the force-EXTEND — so no test drives a real SetDisplayConfig.
Not fixed here, worth knowing
The gaming-rig preset is KeepAlive::Forever + Topology::Exclusive, so on session end the display is Pinned, teardown never runs, and the physical panel stays dark indefinitely — by design, and literally the reporter's "never times out". Escape hatch is POST /api/v1/display/release. Needs a UX decision, not a code fix.
Not proven on glass. The isolate → crash → restart → force-EXTEND cycle was never exercised against a real panel (no Windows box with a lit display was available). Everything above is compile/lint/unit level.
The dark-desk backstop in restore_displays_ccd only counts external_physical, so a laptop internal panel left dark by a failed in-process restore is still not re-lit. The new startup recovery covers the crash case, not that one.
The reporter's blank screen is not root-caused by this PR. PR #37 and #41 are both merged, so a canary user already carries both wire legs; remaining suspects are a host path MTU below the learned floor, a leftover SudoVDA driver from the uninstalled Apollo fighting the isolate, or IddCx slot exhaustion (0x80070490).
Three things a Discord field report (Windows host, 0.1x → canary) turned up. Two are fixed here; the third is diagnosed in the notes below but not fixed.
## 1. "It thinks I have Sunshine/Apollo running" — it didn't
Both were uninstalled. Sunshine's and Apollo's uninstallers leave their `config/` and `logs/` directories in Program Files behind, and `detect.rs` counted a bare directory — or a service registered at **any** start type, including `disabled` — as a live conflict.
The installer's own probe was narrowed to "service start type <= 2" after exactly this aborted a `winget install` in the field, and the tray dropped its always-on warning for the same reason in `3e782852`. The runtime probe never got the same treatment — so the one surface a user actually looks at kept shouting, and **reinstalling Punktfunk could never clear it**, because the leftover is not our state.
`Evidence::is_active` now draws the line (running, or set to start on its own). Only active detections reach the startup warning, the `detect-conflicts` exit code, and `/local/summary`. Dormant findings still appear in the full report, under a heading saying they need no action — that report is where "why does it think I have Apollo?" gets answered.
- Windows reads the real SCM start type via `query_config`; unreadable ⇒ reported dormant (a genuinely running host is caught by the process scan anyway).
- Linux checks for a `.wants`/`.requires` **symlink**, not merely a unit file — an installed-but-not-enabled unit starts nothing.
- `detect-conflicts` now exits 1 only for an active conflict, so installers/support scripts gating on it stop aborting over leftovers.
## 2. The console card claimed "running" regardless of evidence
`host_conflicts_title` was hardcoded to "Another game-streaming server is **running** on this machine", so a leftover folder was announced as a running server. It now says "active", and each entry names what was actually observed — `Sunshine (running)`, `Apollo (starts automatically)`.
## 3. "The exclusive screen never times out re-enabling the display"
`restore_displays_ccd` runs from exactly one place — `teardown_removed`, gated on the **in-memory** `ccd_saved`. Windows deliberately never saves the isolated topology to the CCD database, precisely so teardown can restore the user's layout. So a host that crashed, was killed, or was stopped mid-session left the operator's panels deactivated with nothing in the product to put them back.
There was already a startup recovery leg, but only for the **experimental** `pnp_disable_monitors` axis, which is `false` by default — the default Exclusive path had no recovery at all.
New `isolate_journal`:
- **marks** the deactivated targets *before* the apply (dying mid-apply is inside the window this exists to cover),
- **clears** after the restore — `restore_displays_ccd` wraps an `_inner` so a crash part-way through keeps the marker,
- **`startup_recover()`** force-EXTENDs at host start if a marker survived.
EXTEND rather than replaying the saved CCD blob on purpose: that blob pins the *virtual* display's target id, which dies with the crashed host, so a replay would mostly fail `ERROR_BAD_CONFIGURATION` into the very backstop `restore_displays_ccd` already keeps — and EXTEND stays correct across a reboot, where saved ids are stale anyway.
## Verification
Green on three boxes, each with an on-box proof that the machine compiled these bytes (not a stale tree):
| | Linux `.21` | Windows `.133` | Windows `.173` |
|---|---|---|---|
| `cargo fmt --all --check` | 0 | 0 | 0 |
| clippy `-D warnings` | 0 (`--workspace --all-targets --locked`) | 0 | 0 |
| detect tests | 15/15 | 15/15 | 15/15 |
| `isolate_journal` tests | — (Windows-only) | — | **4/4** |
`isolate_journal` carries 4 unit tests: mark→`pending()`→clear round-trip, empty set writes no marker, an unchanged re-mark does not rewrite (asserted with a sentinel rather than mtimes, which would pass vacuously at coarse filesystem timestamp resolution), and a corrupt marker still requests recovery. They are possible because `pf_paths::config_dir()` honors `PUNKTFUNK_CONFIG_DIR`, and because `startup_recover` was split into `pending()` (the decision) plus the force-EXTEND — so no test drives a real `SetDisplayConfig`.
## Not fixed here, worth knowing
- **The `gaming-rig` preset is `KeepAlive::Forever` + `Topology::Exclusive`**, so on session end the display is *Pinned*, teardown never runs, and the physical panel stays dark indefinitely — by design, and literally the reporter's "never times out". Escape hatch is `POST /api/v1/display/release`. Needs a UX decision, not a code fix.
- **Not proven on glass.** The isolate → crash → restart → force-EXTEND cycle was never exercised against a real panel (no Windows box with a lit display was available). Everything above is compile/lint/unit level.
- The dark-desk backstop in `restore_displays_ccd` only counts `external_physical`, so a **laptop internal panel** left dark by a *failed in-process restore* is still not re-lit. The new startup recovery covers the crash case, not that one.
- The reporter's **blank screen is not root-caused** by this PR. PR #37 and #41 are both merged, so a canary user already carries both wire legs; remaining suspects are a host path MTU below the learned floor, a leftover SudoVDA driver from the uninstalled Apollo fighting the isolate, or IddCx slot exhaustion (`0x80070490`).
Three things a field report (Discord, upgrade from 0.1x) turned up, all on the
Windows host.
1. "It thinks I have Sunshine/Apollo running." It didn't — they were uninstalled.
Both uninstallers leave their config/log directory in Program Files behind, and
`detect.rs` counted a bare directory, or a service registered at ANY start type
(including `disabled`), as a live conflict. The installer's own probe was
narrowed to "service start type <= 2" after exactly this cried wolf on a
`winget install`, and the tray dropped its always-on warning for the same reason
in 3e782852 — the runtime probe never got the same treatment, so the one surface
the user actually looks at kept shouting. `Evidence::is_active` now draws the
line (running, or set to start on its own) and only active detections reach the
startup warning, the `detect-conflicts` exit code, and `/local/summary`. Dormant
findings still print in the full report, under a heading that says they need no
action — that report is where "why does it think I have Apollo?" gets answered.
2. The console's conflicts card hardcoded "Another game-streaming server is
**running** on this machine" regardless of what was found, so a dormant leftover
was announced as a running server. It now says "active", and each entry names
the observation — `Sunshine (running)`, `Apollo (starts automatically)`.
3. "The exclusive screen never times out going back to re-enabling the display."
`isolate_displays_ccd` deactivates the operator's panels and hands the
pre-isolate topology to the caller, which restores it at teardown — but that
snapshot is PROCESS MEMORY, and Windows deliberately never saves the isolated
topology to the CCD database. So a host that crashed, was killed, or was stopped
mid-session left the desk dark with nothing in the product to undo it. There was
one startup recovery leg already, but only for the EXPERIMENTAL
`pnp_disable_monitors` axis, which is off by default — the default Exclusive path
had none. `isolate_journal` now marks what an isolate is about to switch off
(before the apply, so dying mid-apply is covered), clears the mark on restore,
and force-EXTENDs at host startup if a mark survived. EXTEND rather than
replaying the saved blob: the blob pins the virtual display's target id, which
dies with the crashed host, so a replay would mostly fail BAD_CONFIGURATION into
the very same backstop `restore_displays_ccd` already keeps — and EXTEND stays
correct across a reboot, where saved ids would be stale.
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.
Three things a Discord field report (Windows host, 0.1x → canary) turned up. Two are fixed here; the third is diagnosed in the notes below but not fixed.
1. "It thinks I have Sunshine/Apollo running" — it didn't
Both were uninstalled. Sunshine's and Apollo's uninstallers leave their
config/andlogs/directories in Program Files behind, anddetect.rscounted a bare directory — or a service registered at any start type, includingdisabled— as a live conflict.The installer's own probe was narrowed to "service start type <= 2" after exactly this aborted a
winget installin the field, and the tray dropped its always-on warning for the same reason in3e782852. The runtime probe never got the same treatment — so the one surface a user actually looks at kept shouting, and reinstalling Punktfunk could never clear it, because the leftover is not our state.Evidence::is_activenow draws the line (running, or set to start on its own). Only active detections reach the startup warning, thedetect-conflictsexit code, and/local/summary. Dormant findings still appear in the full report, under a heading saying they need no action — that report is where "why does it think I have Apollo?" gets answered.query_config; unreadable ⇒ reported dormant (a genuinely running host is caught by the process scan anyway)..wants/.requiressymlink, not merely a unit file — an installed-but-not-enabled unit starts nothing.detect-conflictsnow exits 1 only for an active conflict, so installers/support scripts gating on it stop aborting over leftovers.2. The console card claimed "running" regardless of evidence
host_conflicts_titlewas hardcoded to "Another game-streaming server is running on this machine", so a leftover folder was announced as a running server. It now says "active", and each entry names what was actually observed —Sunshine (running),Apollo (starts automatically).3. "The exclusive screen never times out re-enabling the display"
restore_displays_ccdruns from exactly one place —teardown_removed, gated on the in-memoryccd_saved. Windows deliberately never saves the isolated topology to the CCD database, precisely so teardown can restore the user's layout. So a host that crashed, was killed, or was stopped mid-session left the operator's panels deactivated with nothing in the product to put them back.There was already a startup recovery leg, but only for the experimental
pnp_disable_monitorsaxis, which isfalseby default — the default Exclusive path had no recovery at all.New
isolate_journal:restore_displays_ccdwraps an_innerso a crash part-way through keeps the marker,startup_recover()force-EXTENDs at host start if a marker survived.EXTEND rather than replaying the saved CCD blob on purpose: that blob pins the virtual display's target id, which dies with the crashed host, so a replay would mostly fail
ERROR_BAD_CONFIGURATIONinto the very backstoprestore_displays_ccdalready keeps — and EXTEND stays correct across a reboot, where saved ids are stale anyway.Verification
Green on three boxes, each with an on-box proof that the machine compiled these bytes (not a stale tree):
.21.133.173cargo fmt --all --check-D warnings--workspace --all-targets --locked)isolate_journaltestsisolate_journalcarries 4 unit tests: mark→pending()→clear round-trip, empty set writes no marker, an unchanged re-mark does not rewrite (asserted with a sentinel rather than mtimes, which would pass vacuously at coarse filesystem timestamp resolution), and a corrupt marker still requests recovery. They are possible becausepf_paths::config_dir()honorsPUNKTFUNK_CONFIG_DIR, and becausestartup_recoverwas split intopending()(the decision) plus the force-EXTEND — so no test drives a realSetDisplayConfig.Not fixed here, worth knowing
gaming-rigpreset isKeepAlive::Forever+Topology::Exclusive, so on session end the display is Pinned, teardown never runs, and the physical panel stays dark indefinitely — by design, and literally the reporter's "never times out". Escape hatch isPOST /api/v1/display/release. Needs a UX decision, not a code fix.restore_displays_ccdonly countsexternal_physical, so a laptop internal panel left dark by a failed in-process restore is still not re-lit. The new startup recovery covers the crash case, not that one.0x80070490).