Field report 2026-08-18 (Windows host on 0.30): PunktfunkScripting task Running, Playnite + Steam plugins installed, library empty, and "no logs at all for plugins" — nowhere on the box. Confirmed cause: the operator had moved the management API off 47990 (PUNKTFUNK_MGMT_BIND, the supported way to share a machine with Sunshine/Apollo).
Why it was silent
The web console followed the move — the host publishes the port it really bound to <config_dir>/mgmt-endpoint on every start (mgmt::publish_endpoint). Nothing else read that file:
Plugin runner / SDK resolved PUNKTFUNK_MGMT_URL → literal https://127.0.0.1:47990. The runner is a scheduled task (Windows) / systemd unit that inherits nothing from host.env — on Windows it cannot even read it — so every plugin and the runner's own log shipper (its only log door) dialed a dead port forever. Task Running, plugins never registering, empty grid, nothing to send when asked for logs.
Tray defaulted --mgmt-port to 47990 and told the operator to edit the autostart command line. Nobody does; it reports a running host as unreachable.
Changes
c0dcac7f — follow the published port, one source, two readers, no new file
sdk/src/config.ts::publishedMgmtUrl reads mgmt-endpoint; resolveConfig order is now {url} → PUNKTFUNK_MGMT_URL → published file → 47990. Every plugin connect() follows on every platform, no unit/task changes. runner-cli.ts also exports it into PUNKTFUNK_MGMT_URL before any plugin loads, so a plugin still carrying an older vendored @punktfunk/host follows too (on Windows reconcileSharedSdk cannot refresh the read-only tree). An explicit PUNKTFUNK_MGMT_URL still wins.
pf_paths::published_mgmt_port (std-only leaf; the tray now depends on it) parses the same line. Tray mgmt_port → Option<u16>: --mgmt-port pins, None re-reads the file on every poll tick, so a host restarted on a new port is picked up without relaunching the tray.
c7c9500e — the Windows runner task writes a log file scripting-run.cmd redirects stdout+stderr to %ProgramData%\punktfunk\plugin-state\runner.log (previous run kept as runner.log.1). plugin-state is the one dir plugins enable makes writable for LocalService and it inherits Users-read. Writability is probed with copy /y nul first; if not writable (task started before plugins enable ever ran) the runner starts unlogged as before rather than not at all. No goto (file is LF; cmd's label scan is unreliable there). Console empty-Plugins hint (en/de) and the plugin docs name the file.
Sweep for the same bug elsewhere
Web console (windows::service::spawn_web, systemd unit, NixOS module) already sourced the file; host CLI, plugin-kit (goes through the SDK), gaming-mode console and native clients derive the port from discovery / the Welcome. No other literal on a loopback path. The console's web port (47992) is not operator-configurable, so the tray's literal there is not the same class.
cargo check -p punktfunk-tray -p pf-paths on Linux (docker rust:1.96-bookworm) — the tray is cfg-gated off macOS.
⚠ Not built or run on Windows from here (no Windows box reachable); the .cmd change is verified by reading only — copy nul as a write probe, if defined blocks, leading redirect on echo.
Field report 2026-08-18 (Windows host on 0.30): `PunktfunkScripting` task Running, Playnite + Steam plugins installed, library empty, and "no logs at all for plugins" — nowhere on the box. Confirmed cause: the operator had moved the management API off 47990 (`PUNKTFUNK_MGMT_BIND`, the supported way to share a machine with Sunshine/Apollo).
## Why it was silent
The web console followed the move — the host publishes the port it *really* bound to `<config_dir>/mgmt-endpoint` on every start (`mgmt::publish_endpoint`). Nothing else read that file:
- **Plugin runner / SDK** resolved `PUNKTFUNK_MGMT_URL` → literal `https://127.0.0.1:47990`. The runner is a scheduled task (Windows) / systemd unit that inherits nothing from `host.env` — on Windows it cannot even read it — so every plugin *and the runner's own log shipper* (its only log door) dialed a dead port forever. Task Running, plugins never registering, empty grid, nothing to send when asked for logs.
- **Tray** defaulted `--mgmt-port` to 47990 and told the operator to edit the autostart command line. Nobody does; it reports a running host as unreachable.
## Changes
**`c0dcac7f` — follow the published port, one source, two readers, no new file**
- `sdk/src/config.ts::publishedMgmtUrl` reads `mgmt-endpoint`; `resolveConfig` order is now `{url}` → `PUNKTFUNK_MGMT_URL` → published file → 47990. Every plugin `connect()` follows on every platform, no unit/task changes. `runner-cli.ts` also exports it into `PUNKTFUNK_MGMT_URL` before any plugin loads, so a plugin still carrying an older vendored `@punktfunk/host` follows too (on Windows `reconcileSharedSdk` cannot refresh the read-only tree). An explicit `PUNKTFUNK_MGMT_URL` still wins.
- `pf_paths::published_mgmt_port` (std-only leaf; the tray now depends on it) parses the same line. Tray `mgmt_port` → `Option<u16>`: `--mgmt-port` pins, `None` re-reads the file on every poll tick, so a host restarted on a new port is picked up without relaunching the tray.
- Docs/comments: `configuration.md`, `troubleshooting.md`, `host.env.example`, SDK README, installer tray comment, `publish_endpoint` doc.
**`c7c9500e` — the Windows runner task writes a log file**
`scripting-run.cmd` redirects stdout+stderr to `%ProgramData%\punktfunk\plugin-state\runner.log` (previous run kept as `runner.log.1`). `plugin-state` is the one dir `plugins enable` makes writable for LocalService and it inherits Users-read. Writability is probed with `copy /y nul` first; if not writable (task started before `plugins enable` ever ran) the runner starts unlogged as before rather than not at all. No `goto` (file is LF; cmd's label scan is unreliable there). Console empty-Plugins hint (en/de) and the plugin docs name the file.
## Sweep for the same bug elsewhere
Web console (`windows::service::spawn_web`, systemd unit, NixOS module) already sourced the file; host CLI, plugin-kit (goes through the SDK), gaming-mode console and native clients derive the port from discovery / the `Welcome`. No other literal on a loopback path. The console's web port (47992) is not operator-configurable, so the tray's literal there is not the same class.
## Verified
- SDK: 83 tests pass (4 new: absent file → default, published line followed, env wins, blank = unset), `tsc --noEmit` clean, biome clean.
- `pf-paths` unit test; `cargo fmt --all --check` clean; `cargo clippy -p pf-paths --all-targets -D warnings` clean.
- `cargo check -p punktfunk-tray -p pf-paths` on Linux (docker `rust:1.96-bookworm`) — the tray is cfg-gated off macOS.
- ⚠ Not built or run on Windows from here (no Windows box reachable); the `.cmd` change is verified by reading only — `copy nul` as a write probe, `if defined` blocks, leading redirect on `echo`.
Field report 2026-08-18, Windows host on 0.30: PunktfunkScripting task Running, Playnite and
Steam plugins installed, library empty, and "no logs at all for plugins" — nowhere on the box.
That is by construction, not by accident. The runner's only log door is the log shipper, which
tees console output to `POST /plugins/logs` over the mgmt API; the scheduled task itself had no
console and no file. So every failure that stops the runner reaching the host — LocalService
lost its read grant on plugin-token / native-cert.pem, a moved mgmt bind, a TLS pin miss, a
401 — is exactly the failure the shipper cannot report, and it leaves the same picture:
task Running, plugins never registering, an empty grid, and nothing to send when asked for logs.
`scripting-run.cmd` now redirects the runner's stdout+stderr to
`%ProgramData%\punktfunk\plugin-state\runner.log`, keeping the previous run as `runner.log.1`.
plugin-state is the one directory `plugins enable` makes writable for LocalService, and it
inherits Users-read from the config dir, so the operator can `type` it from any prompt.
Writability is probed with `copy /y nul` first; if the dir is not writable (the task was started
by the installer before `plugins enable` ever ran) the runner starts unlogged as before rather
than not at all. No `goto`: the file is stored LF and cmd's label scan is unreliable there.
The console's empty-Plugins hint (en/de) and the plugin docs now name the file; the log-ship
header no longer claims the task writes no file. Verified by reading only — no Windows box
reachable from here; the cmd semantics used (`copy nul` as a write probe, `if defined` blocks,
leading redirect on `echo`) are the boring ones.
Field report 2026-08-18, confirmed: the operator had moved the management API off 47990
(`PUNKTFUNK_MGMT_BIND` in host.env — the supported way to share a box with Sunshine/Apollo). The
web console followed, because it reads `<config_dir>/mgmt-endpoint`, the one line the host
publishes on every start with the port it REALLY bound. Nothing else did:
- The plugin runner / SDK resolved `PUNKTFUNK_MGMT_URL` → literal `https://127.0.0.1:47990`.
The runner is a scheduled task (Windows) / systemd unit that inherits nothing from host.env —
on Windows it cannot even read it — so every plugin, and the runner's own log shipper, dialed a
dead port forever. Task Running, plugins never registering, empty library, and "no logs at all".
- The tray defaulted `--mgmt-port` to 47990 and told the operator to edit the autostart command
line if they moved the bind. Nobody knows to do that; the tray reports a running host as
unreachable.
One source, two readers, no new file:
- `sdk/src/config.ts::publishedMgmtUrl` reads `mgmt-endpoint`; `resolveConfig` uses it after the
env override and before the 47990 default. Every plugin `connect()` follows, on every platform,
with no unit/task changes. `runner-cli.ts` additionally exports it into `PUNKTFUNK_MGMT_URL`
before any plugin loads, so a plugin still carrying an older vendored `@punktfunk/host` follows
too (on Windows `reconcileSharedSdk` cannot refresh the read-only tree, so old copies can
outlive several host upgrades). An explicit PUNKTFUNK_MGMT_URL still wins.
- `pf_paths::published_mgmt_port` (std-only leaf; the tray now depends on it) parses the same
line. The tray's `mgmt_port` becomes `Option<u16>`: `--mgmt-port` pins, `None` re-reads the file
on every poll tick, so a host restarted on a new port is picked up without relaunching the tray.
Swept the rest: the web console (`windows::service::spawn_web`, the systemd unit, NixOS module)
already sourced the file; the host CLI, plugin-kit (goes through the SDK), gaming-mode console
and native clients derive the port from discovery / the Welcome — no other literal remained on a
loopback path. The console's web port (47992) is not operator-configurable, so the tray's
literal there is not the same bug.
Verified: SDK 83 tests pass (4 new: absent file → default, published line followed, env wins,
blank = unset), `tsc` clean, biome clean; `pf-paths` unit test; `cargo fmt --check` clean;
`cargo clippy -p pf-paths -D warnings` clean; `cargo check -p punktfunk-tray -p pf-paths` on
Linux (docker rust:1.96) — the tray is cfg-gated off macOS. Not built on Windows from here.
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.
Field report 2026-08-18 (Windows host on 0.30):
PunktfunkScriptingtask Running, Playnite + Steam plugins installed, library empty, and "no logs at all for plugins" — nowhere on the box. Confirmed cause: the operator had moved the management API off 47990 (PUNKTFUNK_MGMT_BIND, the supported way to share a machine with Sunshine/Apollo).Why it was silent
The web console followed the move — the host publishes the port it really bound to
<config_dir>/mgmt-endpointon every start (mgmt::publish_endpoint). Nothing else read that file:PUNKTFUNK_MGMT_URL→ literalhttps://127.0.0.1:47990. The runner is a scheduled task (Windows) / systemd unit that inherits nothing fromhost.env— on Windows it cannot even read it — so every plugin and the runner's own log shipper (its only log door) dialed a dead port forever. Task Running, plugins never registering, empty grid, nothing to send when asked for logs.--mgmt-portto 47990 and told the operator to edit the autostart command line. Nobody does; it reports a running host as unreachable.Changes
c0dcac7f— follow the published port, one source, two readers, no new filesdk/src/config.ts::publishedMgmtUrlreadsmgmt-endpoint;resolveConfigorder is now{url}→PUNKTFUNK_MGMT_URL→ published file → 47990. Every pluginconnect()follows on every platform, no unit/task changes.runner-cli.tsalso exports it intoPUNKTFUNK_MGMT_URLbefore any plugin loads, so a plugin still carrying an older vendored@punktfunk/hostfollows too (on WindowsreconcileSharedSdkcannot refresh the read-only tree). An explicitPUNKTFUNK_MGMT_URLstill wins.pf_paths::published_mgmt_port(std-only leaf; the tray now depends on it) parses the same line. Traymgmt_port→Option<u16>:--mgmt-portpins,Nonere-reads the file on every poll tick, so a host restarted on a new port is picked up without relaunching the tray.configuration.md,troubleshooting.md,host.env.example, SDK README, installer tray comment,publish_endpointdoc.c7c9500e— the Windows runner task writes a log filescripting-run.cmdredirects stdout+stderr to%ProgramData%\punktfunk\plugin-state\runner.log(previous run kept asrunner.log.1).plugin-stateis the one dirplugins enablemakes writable for LocalService and it inherits Users-read. Writability is probed withcopy /y nulfirst; if not writable (task started beforeplugins enableever ran) the runner starts unlogged as before rather than not at all. Nogoto(file is LF; cmd's label scan is unreliable there). Console empty-Plugins hint (en/de) and the plugin docs name the file.Sweep for the same bug elsewhere
Web console (
windows::service::spawn_web, systemd unit, NixOS module) already sourced the file; host CLI, plugin-kit (goes through the SDK), gaming-mode console and native clients derive the port from discovery / theWelcome. No other literal on a loopback path. The console's web port (47992) is not operator-configurable, so the tray's literal there is not the same class.Verified
tsc --noEmitclean, biome clean.pf-pathsunit test;cargo fmt --all --checkclean;cargo clippy -p pf-paths --all-targets -D warningsclean.cargo check -p punktfunk-tray -p pf-pathson Linux (dockerrust:1.96-bookworm) — the tray is cfg-gated off macOS..cmdchange is verified by reading only —copy nulas a write probe,if definedblocks, leading redirect onecho.