Skip to content

Troubleshooting

The fastest recovery steps for the public Polaris host flow. Use the web UI’s Troubleshooting screen first when it is still reachable.

If the stream is running but looks slow, unstable, or blurry, start with Fix a bad stream with Doctor. Keeping the affected stream open lets Doctor separate network, host, and client evidence before you change anything.

Welcome page after an upgrade or reinstall

Section titled “Welcome page after an upgrade or reinstall”

The Welcome wizard is only for a host that has never had a Polaris web account. Package upgrades, package removal, and reinstall intentionally leave credentials, pairing keys, settings, and the library in ~/.config/polaris.

If Welcome reports an error after an upgrade, open https://localhost:47990/#/login and use the previous credentials. On hosts whose guide uses the IPv4 loopback explicitly, use https://127.0.0.1:47990/#/login. Do not delete the configuration directory just to recover web access.

If the old credentials are no longer known, reset them as described below.

Reset the web UI username and password:

Terminal window
polaris --creds new-username new-password

Run this as the same user account that runs Polaris. Do not use sudo unless Polaris itself runs as root, because that can update a different config directory.

Restart Polaris after changing credentials. A running Polaris process keeps the previous credentials in memory until restart.

For packaged user-service installs:

Terminal window
systemctl --user restart polaris

For foreground sessions, stop Polaris and start it again.

Service does not start, or the console keeps an old version

Section titled “Service does not start, or the console keeps an old version”

systemctl --user status polaris reporting status=203/EXEC, or a console that still shows the previous version after a package update, usually means the user service is not running the packaged binary. The Bazzite DRM/KMS recipe points the service at a copy under /usr/local through a drop-in: delete the copy without the drop-in and the service execs a path that no longer exists; update the package and the copy silently stays on the old version.

systemctl --user cat polaris shows the drop-in and its ExecStart. sudo -H polaris --setup-host reports both cases with the fix, the Update Center says “Installed, running a copy” instead of asking for a restart that would change nothing, and the Doctor’s running_binary row (also in the support bundle) names the binary that produced the report.

To run the packaged binary again:

Terminal window
systemctl --user stop polaris
rm -f ~/.config/systemd/user/polaris.service.d/10-bazzite-kms.conf
systemctl --user daemon-reload
systemctl --user start polaris

To keep the copy, refresh it after every update as the Bazzite guide describes. On rpm-ostree hosts the console also shows the old version until the new deployment is booted; rpm-ostree status marks the booted one with ●.

Under polaris.service, a restart asked for from the console or the tray exits with status 75 and the unit starts the installed binary again a few seconds later, so a restart after a package update runs the new version. Started any other way, Polaris re-executes itself in place and keeps its process id. systemctl --user stop and restart always win over a pending restart: the process exits instead of re-executing, so a stop cannot hang until systemd’s timeout.

A custom unit with its own Restart= can opt into the same behaviour with Environment=POLARIS_SERVICE_RESTART=1; POLARIS_SERVICE_RESTART=0 keeps the in-place restart under any unit.

In 1.4.8 a restart request could be lost when the host happened to be running a shell command at that moment. The console then kept the old settings and the log ended at “Shutdown requested: restart requested”. On such a host, systemctl --user restart polaris still works.

  1. Confirm Polaris is running.
  2. Check that you are opening https://localhost:47990 or https://localhost:<port + 1> if you changed port.
  3. Accept the local HTTPS certificate warning in the browser.
  4. Check your local firewall rules if the UI is unreachable from another device on the LAN.

Polaris keeps its config under ~/.config/polaris, so installing it should not remove or overwrite an existing Sunshine setup. Do not run both hosts on the default GameStream/Moonlight ports at the same time unless you intentionally change one host’s port value.

For a quick Polaris test, stop Sunshine first:

Terminal window
systemctl --user stop sunshine
systemctl --user enable --now polaris

If Sunshine runs as a system service on your distro, use the matching system-service command instead. To switch back, stop Polaris and start Sunshine again.

Paired client gets Permission denied (403) when starting a stream

Section titled “Paired client gets Permission denied (403) when starting a stream”

Pairing proves the device’s identity, but its saved access preset still controls whether it can start or control a stream. A device set to Browse & Watch can list apps and watch an existing session, but Desktop and game launch requests correctly return 403.

Open Devices in the Polaris web UI, find the paired device, choose Edit Access, select Game Control, and save. Retry the launch without pairing again. Game Control includes library, launch, keyboard, mouse, touch, pen, and controller input, but does not grant clipboard, file-transfer, or server-command access. Choose Full Control only when the device needs those broader operations.

Polaris now uses Game Control for newly paired Nova and Moonlight-compatible devices. Existing paired devices are never silently upgraded, so a device saved by an older release may still need the one-time access change above.

DRM/KMS exposes the desktop framebuffer and the hardware cursor as separate planes. Polaris must composite that cursor plane into the video stream; otherwise the pointer can remain visible on the host display while disappearing in Moonlight.

Open Settings > Input > Pointer and touch and enable Show host cursor. New or unset configurations enable it by default, while an explicit mouse_cursor_visible = disabled remains unchanged. You can toggle Polaris-controlled cursors for the current runtime with Ctrl+Alt+Shift+N. If a client draws its own local cursor and you see two pointers, disable the host cursor. Portal capture may embed the compositor cursor independently of this runtime toggle.

Confirm these settings first:

headless_mode = enabled
linux_use_cage_compositor = enabled
linux_prefer_gpu_native_capture = enabled

That is the intended Headless Stream path for NVIDIA/NVENC and AMD/Mesa VAAPI hosts that can keep frames GPU-resident. It avoids touching your normal desktop layout and reduces display mode churn after a session ends. If the stream is stable but logs report SHM/RAM capture, treat that as a performance/capability fallback first, not a startup failure. If enabling GPU-native capture blocks launch on a specific driver/compositor stack, temporarily set it to disabled and include the capture decision fields in the bug report.

The built-in Desktop entry streams your existing KDE, GNOME, or wlroots desktop even when the host default is a private stream, because it has Mirror the host desktop turned on in the app editor. An entry with no command and that setting off opens this private compositor instead. If such an entry connects but shows an empty or black desktop while app entries work, the headless runtime is alive and nothing visible has been launched in it yet; right-click the empty screen to open the session menu.

Unsure which mode you should be running in the first place? Start with Launch modes and capture paths.

NVIDIA true-headless first launch fails with 503

Section titled “NVIDIA true-headless first launch fails with 503”

On NVIDIA/NVENC hosts running true-headless labwc with GPU-native capture disabled, the very first launch can fail with a 503 encoder-initialization error even though NVENC is healthy. On headless paths the encoder probe is deferred and served from an on-disk cache; when that cache is cold or missing, priming it can fail and the launch is refused. The host configuration warnings surface this as nvidia_headless_gpu_native_disabled.

The fix matches the warning’s own advice: set linux_prefer_gpu_native_capture = enabled (or pick the Private Stream (GPU-native) card in the web UI), restart Polaris, and retry, before chasing CUDA or NVENC driver issues.

Fullscreen Proton or Wine game renders on the physical monitor

Section titled “Fullscreen Proton or Wine game renders on the physical monitor”

The stream connects, audio and input reach the private session correctly, and the client shows an empty compositor while the game appears on your real desktop instead.

If the app is launched through Flatpak, this is the Flatpak portal replacing DISPLAY. Polaris exports the private session’s display to the command it launches, but when that command spawns back out through the portal (org.freedesktop.portal.Flatpak), the portal builds the new sandbox from the portal service’s own environment. The portal service is D-Bus activated and holds the display your desktop session had at login, so it overwrites DISPLAY and binds only that one X socket into the container. Wine and Proton use the X11 driver by default, follow the substituted DISPLAY, and land on the host desktop.

Nothing set at any layer above the portal survives this. Passing the variable explicitly does not help either, because the portal applies its own X11 arguments after the caller’s environment.

Confirm it in one command while the game is running, using the game’s own mount namespace:

Terminal window
GAME=<game-pid>
sudo ls -la /proc/$GAME/root/tmp/.X11-unix/
sudo tr '\0' '\n' < /proc/$GAME/environ | grep -E '^(DISPLAY|WAYLAND_DISPLAY)='

If the only socket present is the one for your desktop session rather than the private session’s, the container never had a path to the private display.

Workaround: launch through a native, non-Flatpak build of your launcher (umu-launcher, Lutris, Heroic, Steam). The Steam Runtime container itself honors DISPLAY correctly, so removing the portal hop is enough. A direct flatpak run also passes the display through correctly; it is specifically the portal spawn underneath a Flatpak launcher that does not.

Two things commonly trip up the switch from a launcher’s Flatpak build to its native one:

  • Re-add the game rather than reusing its old ID. Heroic gives sideload entries a new app_name per install, so an identifier copied from the Flatpak install will not resolve in the native one. Read the current value out of ~/.config/heroic/sideload_apps/library.json, or your launcher’s equivalent.
  • Let the launcher finish starting before handing it a launch URL. Native Heroic given a heroic://launch?... URL on a cold start throws Cannot read properties of undefined (reading 'getGame') rather than queuing the request. Start the launcher on its own first and send the launch as a separate command.

A working app entry on the reporting host ended up as:

WAYLAND_DISPLAY=wayland-1 DISPLAY=:2 heroic --no-gui "heroic://launch?appName=<id>&runner=sideload"

Polaris exports both of those variables into the private session already, so setting them by hand is redundant rather than required. They are shown here because that is the entry that was verified.

Polaris logs a warning at launch when an app command can reach the portal. If the private compositor does not expose a managed window within the observation period, Polaris reports that it could not confirm attachment. Check the client image before applying the portal workaround: some fullscreen or XWayland surfaces can be visible even when the compositor’s managed-window detector cannot enumerate them, while a genuinely empty client image still points to the host-desktop escape described above.

Steam Big Picture black screen or tiny window

Section titled “Steam Big Picture black screen or tiny window”

Clear Steam’s HTML cache:

Terminal window
rm -rf ~/.local/share/Steam/config/htmlcache/

If you are using MangoHud, disable it for Steam Big Picture and Steam/Proton launches first. Those paths are the most sensitive to early helper-process crashes.

The udev rules and modules-load configuration ship as package files, so virtual input works after the next reboot with nothing else to run. To use it without rebooting first, load the modules once:

Terminal window
sudo modprobe uinput uhid

If the problem persists, ensure your user has access to the input stack expected by your distro setup:

Terminal window
sudo usermod -aG input "$USER"

Then sign out and back in.

On an ostree host — Bazzite, Bluefin, Silverblue and relatives — that command does nothing useful: the input group lives in /usr/lib/group rather than /etc/group, so usermod finds no group to add anyone to. Universal Blue images ship a recipe that copies the definition across first:

Terminal window
ujust add-user-to-input-group

Polaris detects this and prints whichever command applies to your host.

If Polaris was installed before the rules became package files, an older copy may still sit in /etc/udev/rules.d/60-polaris.rules. /etc overrides the packaged file, so that copy keeps shadowing later fixes — including the seat isolation rules, which then never apply no matter what the configuration says.

Terminal window
sudo -H polaris --setup-host

Host setup removes that copy only when its contents still match the file this Polaris ships. An older version’s copy does not match — that is what upgrading changed — so it is kept, with a warning naming the file, because nothing can tell it apart from a copy you edited yourself. Upgrading is therefore the case most likely to leave a shadowing file behind.

If you did not edit it, remove it and reload:

Terminal window
sudo rm /etc/udev/rules.d/60-polaris.rules
sudo udevadm control --reload-rules

Then confirm the packaged rules are the ones in effect:

Terminal window
grep -c seat-isolated /usr/lib/udev/rules.d/60-polaris.rules

Client input also types into the host desktop

Section titled “Client input also types into the host desktop”

A private stream session and the desktop session logged in at the machine both see the virtual keyboard and mouse Polaris creates, so a client’s typing reaches both. Enable client_keyboard_mouse_seat_isolation to assign those devices to a dedicated seat, or ignore them by name in your desktop compositor. See host and private session input isolation.

Local desktop audio is captured during a headless stream

Section titled “Local desktop audio is captured during a headless stream”

In headless labwc sessions, Polaris routes launched apps to the Polaris virtual sink and captures that sink directly instead of changing the user’s global default audio output. The healthy log path looks like:

Linux audio isolation: routing launched apps to virtual sink [sink-sunshine-stereo] without changing the user's default sink
Linux audio isolation: capturing virtual sink without changing the user's default sink

If local Plasma/GNOME audio is still mixed into the stream, include the audio section of the logs and whether the client requested host audio. Host-audio mode intentionally captures the host sink, so same-user local apps can still be part of that stream.

If the log shows this warning during an otherwise working stream:

Thread priority elevation unavailable; continuing with the default scheduler

Polaris is running, but the user service cannot raise capture, encode, or audio worker priority. Packaged installs include LimitRTPRIO=95 and LimitNICE=-10 in polaris.service; reload the user manager and restart Polaris after updating the package:

Terminal window
systemctl --user daemon-reload
systemctl --user restart polaris

If the warning remains, the user manager inherited stricter limits from the login session. Confirm the active unit with:

Terminal window
systemctl --user cat polaris
journalctl --user -u polaris -b --no-pager | grep -E 'Thread priority|RealtimeKit|SCHED_FIFO'

Installing and running RealtimeKit can also allow priority elevation without granting broad capabilities to the Polaris binary.

The Doctor reports this as a host configuration warning too, with the limits that applied, so a support bundle exported after the stream still has it.

Packaged Linux user units are ordered with graphical-session.target and pass through common desktop environment variables such as WAYLAND_DISPLAY, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS. In private Headless Stream mode, a missing parent WAYLAND_DISPLAY is logged as a limited desktop-preview/portal warning instead of a stream startup failure because Polaris starts its own labwc Wayland socket for the client session.

KMS capture refused for a missing capability

Section titled “KMS capture refused for a missing capability”

KMS/DRM capture reads framebuffers straight from the kernel, which needs CAP_SYS_ADMIN on the Polaris binary. That is deliberately opt-in: the package does not grant it, the host setup step does. Installing or updating the package replaces the binary, and the new one does not carry the capability, so run the step again after every install or update. With capture = kms and no capability, Polaris finds the display, logs Failed to gain CAP_SYS_ADMIN and Couldn't get handle for DRM Framebuffer, and then either substitutes another backend or, when nothing else can capture, serves with no capture at all and H.264 as the only codec. The Doctor reports both cases as kms_capture_needs_capability.

sudo -H polaris --setup-host --enable-kms

then restart Polaris. KMS capture is the path that carries HDR, so keep it if HDR is the goal.

If KMS capture gives a black screen on NVIDIA, confirm the kernel is using:

nvidia_drm.modeset=1

If you do not need DRM/KMS capture, keep using the default compositor and portal paths instead. A startup warning that says KMS probe could not access DRM framebuffer handles; continuing with non-KMS capture backends when available is informational for portal/compositor users; do not apply setcap unless you intentionally selected KMS capture.

If a manually copied explicit-KMS test binary still logs Failed to gain CAP_SYS_ADMIN after setcap, check the mount options for the binary path. File capabilities are ignored on nosuid mounts, so /tmp builds can be misleading; copy the test binary to a normal path such as /usr/local/bin before applying setcap.

For low-FPS NVIDIA headless reports, check Build features: cuda=... first. If the log says cuda=disabled and later shows Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU, the stream is taking an extra CPU copy/upload path. Use a CUDA-enabled package or rebuild with -DPOLARIS_ENABLE_CUDA=ON before comparing headless performance against Sunshine. The host Doctor reports the same fact before any stream as capture_copies_through_system_memory with cause build_without_cuda; see Capture is on the CPU.

The expected fast-path markers for NVIDIA true-headless testing look like this:

Build features: cuda=enabled
labwc: Starting in headless mode
wlr: Using ext-image-copy-capture DMA-BUF for headless labwc
capture_transport=dmabuf frame_residency=gpu
target_device=cuda target_residency=gpu

display_preview: Failed to capture cage screenshot is the web dashboard preview path, not the stream capture path. Repeated failures are rate-limited in the log, and the dashboard backs off preview refreshes after failed captures. If the preview is missing, confirm grim is installed with command -v grim.

For what the capture paths mean in plain terms and which mode fits your GPU, see Launch modes and capture paths. For capture performance, check /polaris/v1/session/status; its capture object includes path, reason, reason_message, cpu_copy, gpu_native, and nested decision fields. /polaris/v1/stream-policy exposes the same data as capture_path, capture_path_reason, capture_path_reason_message, capture_cpu_copy, capture_gpu_native, and capture_decision. A reason such as headless_shm_fallback means Headless Stream is healthy enough to run but still using the conservative SHM/system-memory path. headless_extcopy_dmabuf is the true-headless DMA-BUF path, and gpu_native_requested_shm_fallback means GPU-native capture was requested but the Wayland capture path still fell back to SHM. Support bundles include the same normalized decision data under capture.decision and stream stats capture_decision so a report captures the selected path, reason message, transport, residency, runtime backend, effective headless state, and GPU-native override state.

For LTS distro expectations and package caveats, see the Linux LTS Headless Fallback Matrix. Xvfb or gamescope should be treated as investigation-only unless this supported labwc path cannot cover a confirmed target environment.

A freeze that needs the power button is a kernel or GPU driver lockup, not a Polaris crash, and the journal usually has nothing after it. On a laptop with an integrated GPU and an NVIDIA card there are three drivers in play during a Mirror Desktop stream: the iGPU reading the screen back for the compositor’s screencast, the NVIDIA card taking the frames for NVENC, and the compositor serving both. Test in this order, one change at a time, and stream after each:

  1. Is the machine dead or only the screen? Toggle Caps Lock, or ping the host from a phone. A live host can be reached over SSH during the “freeze” and journalctl -k -f names the driver.
  2. Keep the web console closed during the test. It used to poll the display list through the compositor every three seconds; current releases cache it, older ones do not.
  3. Intel iGPU: add intel_iommu=igfx_off to the kernel command line and reboot. Comet Lake and nearby generations are known to hard-freeze with VT-d active for graphics.
  4. Take the NVIDIA card out: adapter_name = /dev/dri/renderD128 (the compositor’s node, the Doctor names it) and encoder = vaapi. No freeze means the NVIDIA side is the trigger.
  5. Keep NVENC but stop the sleep/wake cycle: options nvidia NVreg_DynamicPowerManagement=0x00 in /etc/modprobe.d/nvidia-pm.conf, rebuild the initramfs, reboot.

Unload any other streaming stack’s virtual display module first (lsmod | grep -E 'hermes|vibeshine|evdi'), so there is one variable fewer. After the next freeze, before starting Polaris again, keep ~/.config/polaris/polaris.log.backup: it is the run that was streaming, and Polaris overwrites it at the next start.

Mission Control reads SHM or system memory, the Doctor’s capture row says shm_cpu_capture, and encode times sit at 4 ms and up where a GPU-native stream would show about 1 ms. The host Doctor now says this before the first stream, as capture_copies_through_system_memory with a cause, read from the configuration, the build and the capture backend the host selected at startup. Each cause has one fix.

cause what is happening fix
build_without_cuda The binary was built without CUDA, so on NVIDIA every capture path copies each frame through system memory before NVENC. polaris --version prints Build features: cuda=disabled; each session logs Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU. Stream mode and the GPU-native setting cannot change it. Install a package built with CUDA. The official Fedora, Arch and Ubuntu packages are; a source build needs -DPOLARIS_ENABLE_CUDA=ON. Only the NVIDIA driver is needed at run time, not the toolkit.
x11_capture The host session is X11 and capture runs through x11grab, which is a system-memory path by construction. Stream from a Wayland session, or use a Private Stream mode, which captures Polaris’ own compositor. capture = nvfbc keeps X11 capture on the GPU on NVIDIA cards that expose NvFBC.
headless_dmabuf_unavailable Private Stream runs the hidden headless compositor, and its last attempt on this host could not hand frames over as DMA-BUF, so capture fell back to SHM. Pick Private Stream (GPU-native) in Play Setup for one launch, or set linux_prefer_gpu_native_capture = enabled and restart: Polaris then runs the private compositor windowed, where DMA-BUF capture works.
windowed_dmabuf_unavailable The private compositor already runs windowed for GPU capture and the last DMA-BUF probe failed. This path needs wlr-export-dmabuf from labwc and a driver that can import the buffer. Send a support bundle from one stream; it carries the import error.
vaapi_system_memory_by_design AMD and Intel: every VA-API capture path takes one copy per frame on purpose, because the DMA-BUF import into the encoder has crashed or stalled on AMD hosts (#367) and stays off until affected hosts prove it safe. Reported as info. Nothing. If throughput falls short at high resolution or refresh, lower resolution, frame rate or bitrate first. POLARIS_PORTAL_DMABUF=1 opts the portal path into the unvalidated DMA-BUF route with no automatic fallback.

Mirror Desktop and Host Virtual Display on KDE or GNOME capture through the desktop portal. With CUDA or Vulkan the portal is asked for DMA-BUF and the compositor decides; KDE handed over system memory in testing. The forecast says nothing for that case, and the session’s capture_transport= log line says which it got.

The forecast is silent until Polaris has evaluated its capture backends at startup, and it can only tell NVIDIA from AMD once an encoder is chosen: with encoder left on auto and a headless mode, that is the first launch. linux_gpu_profile.capture_forecast in the support bundle carries the backend, the encoder, build_has_cuda and the verdict.

If Polaris cannot hold the preferred hardware path, open Mission Control or Troubleshooting and check the active runtime path. Polaris surfaces when capture or encode falls back so you do not need to guess from a black-box client session.

If the log says stream_hdr_enabled=false, treat that stream as SDR. A client HDR request or hdr_mode = 2 can still move the encoder into a 10-bit/P010 path, but it does not make a non-HDR Linux capture path into a true HDR source. On AMD VAAPI systems, keep hdr_mode = 0 and disable client HDR requests until SDR colors are correct, then test HEVC Main 8-bit before testing Main10.

For true HDR, look for all of these lines in the same launch:

HDR metadata: available=true usable=true
Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ)
HDR decision: ... display_hdr=true hdr_metadata_available=true stream_hdr_enabled=true

If stream_hdr_enabled=false, Polaris is being conservative: the client may have requested HDR or Main10, but the active Linux display path did not provide enough metadata to advertise a real HDR stream. If usable=false, the display path exposed an HDR metadata blob, but Polaris rejected it because core static metadata such as display primaries or max display luminance was missing.

stream_hdr_enabled=false on every launch, whatever you toggle, is six independent gates and any one of them is enough. Check them in this order; each has a line in journalctl --user -u polaris that names it.

gate what the journal says fix
capture backend cannot report HDR HDR decision: ... display_hdr=false with capture = wlr or unset on a private mode capture = kms
stream mode captures Polaris’ own compositor session_runtime: ... effective_headless=true Mirror Desktop, Host Virtual Display, Desktop Takeover or Gamescope
binary lacks CAP_SYS_ADMIN Failed to gain CAP_SYS_ADMIN, Couldn't get handle for DRM Framebuffer [...]: Probably not permitted sudo -H polaris --setup-host --enable-kms, restart
client forced off on the host Doctor hdr_disabled_by_saved_setting; client_profiles.json hdr: false or device_db.json hdr_capable: false clear both, or let the client’s own HDR10 report win (1.4.8)
client never asked portal HDR force -> 0 from enable_hdr=false, client_dynamic_range=0 turn on Request HDR in the client; in Nova it is off by default
Host Virtual Display got a KWin screen Doctor hdr_unavailable_on_kwin_virtual_screen; HDR was requested, and a KWin virtual screen carries none KWin virtual screens have no HDR; use Mirror Desktop from an HDR monitor with capture = kms

When all six pass, the session logs HDR metadata: available=true usable=true, Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ) and stream_hdr_enabled=true after Session started for [...]. The encoder probe logs the same lines earlier even when the session will not, so read the ones after the session starts.

On KDE Plasma 6, Host Virtual Display gets a new screen from KWin (see Launch modes and capture paths). The Virtual Display panel on the Audio/Video tab names the backend in use and, when it cannot run, why. The Doctor names three problems here on its own: hvd_kwin_screen_unused when Plasma got another backend, and why; hvd_screen_scaled when the stream screen runs at a scale other than 100%; and hvd_input_not_mapped when touch or pen could not be put on the stream screen.

  • KWin does not offer its screencast protocol to Polaris. KWin only offers it to a program a desktop entry names. Polaris writes one to ~/.local/share/applications the first time and waits a few seconds for KWin to pick it up. If a launch still misses it, Polaris checks again after half a minute, so try again then. If it keeps failing, check that the entry exists and that its Exec line is the Polaris binary that is running.
  • The stream runs at 60 Hz when the client asked for more. A KWin screen starts with one 60 Hz mode, and Polaris adds the client’s rate as a custom mode with kscreen-doctor. The log says did not take mode when KWin refused it.
  • KWin says it cannot tell which program Polaris is, or the automatic order borrows a monitor instead. KWin offers a screen only to a program it can match to its permission entry, and it cannot match one that holds file capabilities, such as the cap_sys_admin that --enable-kms grants for KMS capture. On the automatic order such a Polaris skips the KWin screen and falls back to EVDI when it can create a display, otherwise to kscreen-doctor. Set Backend to KWin (linux_virtual_display_backend = kwin), leave capture on auto or portal, and restart Polaris: it then drops the capability at start. KMS capture and a KWin screen do not go together.
  • kscreen-doctor is not installed. It comes with Plasma. Polaris needs it to place the screen beside your monitors at scale 1; without it KWin can put a new screen on top of your monitor.
  • A game opened on my monitor instead of the stream. Check the log for the backend: with using backend: EVDI on Plasma, Backend is set to evdi or the KWin screen could not start, and an EVDI screen is a monitor like any other, so games open on your primary one; set Backend back to Automatic or to KWin. On the KWin screen, Polaris moves windows onto the stream screen with a small KWin script it loads for the stream. The log says new windows will not be moved when KWin refused to load it or dropped it; the stream then shows an empty screen while the game runs on your monitor. With two Polaris screens at once, the newer one gets new windows. A window the script moves also gets the focus, so the controller drives the game from the first press.
  • A tap from the client landed on another monitor, or the game ignored it. KWin spreads a touch screen and a pen over every monitor unless each is tied to one, so Polaris ties the client’s to the stream screen while it exists (the log says lands on) and unties them when it goes. KWin remembers the tie in ~/.config/kcminputrc under each device’s name, so at the start of every stream on Plasma Polaris also undoes a tie to one of its own screens that a crash left behind; a tie you made yourself in System Settings is kept. The Doctor’s hvd_input_not_mapped carries KWin’s answer when the tie failed; the usual cause is a Polaris started outside the Plasma session, which cannot reach KWin on that session’s bus. A mouse in absolute mode is not tied: KWin places it over the whole desktop whatever it is told, so use touch, or relative mouse mode, on a desk with more than one monitor.
  • My monitors moved when the stream started. KWin keeps a layout for each set of screens and applies it when the set changes, and the one it kept for “your monitors plus the stream screen” can have your monitors somewhere else. Polaris puts every monitor back where it was in the same step that places the stream screen, and the log says did not go back where they were when KWin would not. When the stream ends KWin applies the layout it kept for your monitors alone.
  • The stream screen is at 135% (or another scale). An EVDI screen on Plasma gets the scale KWin stored for it, and the Doctor says hvd_screen_scaled. While a stream runs, set that screen to 100% under System Settings, Display & Monitor; KWin keeps it for next time. Polaris’s own KWin screen is always put at 100%.
  • A window I opened at the desk jumped to the stream. While a stream runs, every new application window is moved onto its screen. Drag it back, or use Meta+Shift+Left. The desktop’s own prompts stay on your monitor: polkit password prompts, ksshaskpass, KWallet and KRunner.
  • My second monitor’s desktop or panel moved. Plasma gives each screen rank its own desktop and panel. Polaris ranks the stream screen after all of yours and keeps your screens in their order; the log says not ranked as they were when KWin would not keep that ranking.
  • Choosing another backend. Set Backend on the Virtual Display panel (linux_virtual_display_backend) to evdi or kscreen to use that one instead; the choice applies without a restart.

A device with a Display Mode Override saved on the host gets that mode whatever it asks for, so choosing 1080p on the client changes nothing. When the override replaced a different request, the Doctor checklist shows a Display mode warning naming the mode the client asked for, and Session Snapshot’s Display mode tile shows both. Clear Display Mode Override for that device on the Devices page, or set it to the mode you want; see Devices. A launch that carries the client’s own resolved profile, as Nova’s does, keeps the client’s mode, so the same device can behave differently in Nova and Moonlight.

Session Snapshot’s Network path tile says how the client reached this host. The Doctor and support bundles carry the same thing as client_network_path: lan, cgnat (the shared 100.64.0.0/10 range, which Tailscale uses on IPv4), tailscale (its IPv6 range), link-local, public or loopback. It stays after the stream ends, so a support bundle exported afterwards still has it, and it never records the address itself.

A tailnet stream is fine while Tailscale connects the two machines directly. When it cannot, it relays the traffic through its DERP servers, and the stream stutters and its latency climbs. Run tailscale ping with the host’s name on the client: via DERP in the answer means the stream is relayed, an address and port mean it is direct.

A client that could not start a stream used to see error 503 and one generic sentence, whatever the host knew. The host now sends the reason as the message the client shows, with the one change that fixes it, and Nova shows both in its launch sheet. The error_code names below are stable, so they can be searched for here and in support threads.

error_code what happened on the host fix
encoder_probe_failed No video encoder could start; on NVIDIA the message adds the driver detail when the driver is the reason Check the Doctor’s Encoder and Capture rows. Against the private compositor: pick Private Stream (GPU-native) or set linux_prefer_gpu_native_capture = enabled
no_capture_backend No capture backend works in the configured stream mode, so nothing could be probed Check capture against the stream mode; unset lets Polaris pick. The Doctor names the missing protocol
capture_backend_unavailable The launch asks for a capture backend that cannot capture anything in its stream mode, such as capture = wlr in Mirror Desktop on KDE or GNOME Set Force a Specific Capture Method under Advanced to Autodetect, or use a stream mode that backend can serve
kms_capture_needs_capability capture = kms without CAP_SYS_ADMIN on the binary sudo -H polaris --setup-host --enable-kms, restart
desktop_capture_not_prepared The screen sharing prompt was declined, or desktop capture could not be prepared Approve the prompt on the host desktop, or use a Private Stream mode
private_runtime_unavailable labwc (or gamescope) is not installed for the chosen mode Install it, or use Mirror Desktop
private_runtime_start_failed, private_runtime_socket_missing The private compositor did not start, or started without a Wayland socket The host journal has the compositor’s own error; restart Polaris and retry
gamescope_session_failed The nested gamescope session did not start, or timed out Check gamescope on the host; the Doctor has a Gamescope helper report
virtual_display_failed, virtual_display_unavailable Host Virtual Display could not be created, or no backend exists The message carries the reason (usually the evdi module); Private Stream needs no virtual display
desktop_takeover_failed, desktop_takeover_recovery_pending Desktop Takeover could not start, or the previous one is still restoring the display Wait for the host display to return; the Doctor’s display warning names the reason
session_stopping, session_state_changed, launch_cancelled, previous_session_cleanup_pending, steam_shutdown_pending, virtual_display_recovery_pending The previous session, Steam, or a display is still being torn down Wait a few seconds and launch again; restart Polaris if it persists
emulator_not_installed A game imported from a ROM folder names an emulator this host does not have, or the emulator file the folder points at is gone Install the emulator from ROM folders under Import Games (Flathub emulators have an install button), or put the folder’s emulator file back
child_tracking_failed, no_active_session The app started but could not be tracked, or a resume found nothing to resume Launch again; send a support bundle if it repeats

Moonlight clients see the same message in their own error dialog; only the code and action attributes are Nova’s.

The Doctor & Support page offers three recovery actions, ordered from least to most disruptive. Try them in order and stop at the first one that gets the stream back.

  1. Force Close ends the running app. Use it when a client says an app is already running or a game hangs on the host while Polaris itself is fine. Connected clients see the stream end or return to the app list, and the app list reloads.
  2. Restart Polaris restarts the host service. Use it when the host stops answering, pairing fails, or capture never starts. Every active stream ends and the web UI reconnects on its own once the service is back.
  3. Quit Polaris stops the service entirely. Use it only when you can start Polaris again another way, such as a terminal or the desktop launcher, because the web UI cannot bring it back once it has exited.

On Windows the ladder has an optional fourth step, Reset Persistent Display Device Settings, for a host stuck restoring a changed display configuration.

The self tests on the Doctor & Support page answer three player questions without opening the raw evidence.

  • Network Path Tester reads the live round trip and packet loss for the current client, checks whether the control and stream ports answer, and suggests a bitrate ceiling the measured path can carry. Open Advanced evidence for the per-check detail and the native probe output.
  • Controller/Input Tester shows whether the browser or the host sees a controller. Detect controller samples the browser gamepad API, Log A press records a manual button sample, and Test rumble pulses haptics when the browser exposes them.
  • Post-session Stream Report summarises the last completed stream: the loudest signal, who owned the issue, and the launch profile to try next.

Copy self-test summary puts all three results on the clipboard in the same form the support bundle uses.

The Troubleshooting screen in the Polaris web UI inspects this host. It is the place to go when something is wrong and you want to know what Polaris thinks happened. The Troubleshooting guide on papi-ux.com explains the problems themselves and is the place to go when you know what is wrong and want to fix it.

When reporting a bug, open Troubleshooting and use Report a problem. It downloads a redacted support bundle and opens a pre-filled GitHub issue with your host OS, GPU and driver, client, and runtime already answered. Attach the bundle to that issue.

Nothing is sent anywhere on its own. The bundle lands on your machine and the issue opens as a draft you complete, so you see exactly what you are sharing before anyone else does.

The bundle carries the current run’s log, the two runs before it (polaris.log.backup and polaris.log.backup.1 next to the log), and the kernel’s GPU-related lines for this boot and the previous one when the journal is readable by your account. A freeze, a reboot and an export used to cost the run that had been streaming; it is now the second retained run. When the journal is closed to your account the bundle says so and names the command to run instead.

If you would rather assemble it yourself, the same screen offers the bundle and the issue draft as separate downloads, and describing the active route, capture backend, encoder, and client device by hand is still useful.

Values whose name reads as a credential are replaced before anything leaves the browser: whole words such as password, token, secret, cookie, auth and credential, run-together forms such as apikey, and key when something qualifies it, as in api_key. Names that merely contain one of those, such as keyboard, stay readable so the bundle remains worth reading. The exact rule is stated in the bundle itself under redaction_notice.

Network addresses are replaced too, in the bundle, the pre-filled issue and copied support text. Each one becomes a label that keeps what kind of address it was, such as [lan-1], [cgnat-1] (the range Tailscale uses on IPv4), [tailscale-1], [link-local-1] or [public-1], and the same address keeps the same label throughout one export. That keeps the part that usually matters, such as a client that moved from your LAN to a tailnet, without saying which address it had. Loopback, multicast and example addresses stay as they are.

Redaction is not a promise that a bundle is safe to publish unread. Look at it first.

Polaris records how each run ended, so the first question is already answered for you. Open Troubleshooting after restarting: if the previous run did not shut down normally, the page says so at the top, and reports one of

  • crashed, meaning it died on a named fatal signal, with the captured backtrace,
  • unclean, meaning it never recorded an exit and left no crash evidence, which usually means the OOM killer, a SIGKILL, or power loss rather than a fault in Polaris.

That distinction matters and is hard to make by hand, which is why Polaris makes it.

The log of the run that crashed is preserved too. The active log describes the run you are looking at now, so the interesting one is the retained copy from the previous run, and the support bundle carries it.

The backtrace Polaris captures comes from a stripped binary, so it names addresses more than functions. For a symbolised one, the matching debug package and coredumpctl still work exactly as before, because the crash handler re-raises rather than swallowing the signal:

Terminal window
sudo pacman -S polaris-debug
coredumpctl info polaris

Include that verbatim alongside the bundle when a maintainer asks for it.

The host config lives in ~/.config/polaris, the service logs can be captured from your systemd user journal, and the run-state and crash evidence sit next to the config as last_run.json and last_crash.txt.