# WSL UI Troubleshooting Guide This guide documents known issues and their solutions encountered during development and usage. --- ## MSIX installation: untrusted publisher or missing certificate For GitHub MSIX releases, download the matching `.cer` and `Install-WSL-UI_.ps1` alongside the signed package. Follow the [MSIX installation guide](MSIX-SIGNING.md) to trust the certificate once as an administrator, then install as your normal user. Windows may describe the same untrusted-root condition as `NotTrusted` or `UnknownError`; the helper verifies the signature again after trust is established. If the helper reports an unsigned package, use the regular `.msix`, not the `_store.msix` copy intended for Partner Center. Older unsigned releases cannot be installed by importing a newer release's certificate. If the signer does not match the supplied certificate, download both files again from the same release. Corporate policy may require your IT administrator to approve sideloading. ## GPU Containers — NVIDIA CUDA passthrough setup {#gpu-containers} WSL-UI detects whether NVIDIA CUDA is available in a distro and whether the NVIDIA Container Toolkit and CDI spec are configured. This section explains what each status means and how to fix common problems. ### Understanding the status rows | Status | Meaning | |---|---| | **NVIDIA CUDA** | `/usr/lib/wsl/lib/libcuda.so.1` is present — your Windows NVIDIA driver injects CUDA into WSL2 | | **NVIDIA Container Toolkit** | `nvidia-ctk` is installed in the distro | | **CDI Specs** | `/etc/cdi/nvidia.yaml` exists and `nvidia-ctk cdi list` returns devices | ### Setup for different configurations #### Podman via Podman machine (most common on Windows) If your WSL2 distro uses Podman machine as a backend (check with `podman system connection list`), containers run inside `podman-machine-default`, not your distro. The CDI spec must be configured there. Inside `podman-machine-default`: ```bash # Install nvidia-ctk if not present (Fedora/RHEL) sudo dnf install -y nvidia-container-toolkit # Generate CDI spec sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml # Verify nvidia-ctk cdi list podman run --rm --device nvidia.com/gpu=all ubuntu:22.04 nvidia-smi ``` > **Note:** WSL-UI's toolkit status reflects the selected distro, but if that distro uses Podman machine, you must run the above commands in `podman-machine-default` directly. #### Podman or Docker running directly in your WSL2 distro If containers run directly in your distro (no Podman machine), install and configure in that distro: **Ubuntu / Debian:** > **Prerequisites:** `curl`, `gnupg`, and `ca-certificates` must be installed first. > On minimal Ubuntu distros (including Ubuntu 25.10+) `gnupg` is not present by default — > without it `gpg --dearmor` silently writes a raw ASCII file and `apt-get update` fails > with `NO_PUBKEY DDCAE044F796ECB0`. Install them first: > ```bash > sudo apt-get update && sudo apt-get install -y curl gnupg ca-certificates > ``` ```bash sudo apt-get install -y curl gnupg ca-certificates curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml ``` **Fedora / RHEL:** ```bash curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo \ | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo sudo dnf install -y nvidia-container-toolkit sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml ``` Then test (add `--network=host` if you hit an nftables networking error — see below): ```bash podman run --rm --network=host --device nvidia.com/gpu=all ubuntu:22.04 nvidia-smi ``` ### Error: `did you mean table 'nat' in family ip?` (nftables not available) When running Podman directly in a WSL2 distro as root, you may see: ``` internal:0:0-0: Error: No such file or directory; did you mean table 'nat' in family ip? ``` **Cause:** Podman's default network backend (netavark) uses nftables for container networking. The WSL2 kernel does not load nftables modules. **Fix:** Add `--network=host` to your `podman run` commands. In WSL2, host networking is perfectly reasonable — your distro already shares the Windows network stack, so there is no meaningful isolation to lose. ```bash podman run --rm --network=host --device nvidia.com/gpu=all ubuntu:22.04 nvidia-smi ``` For GPU workloads (Ollama, CUDA, model inference) where you don't need container-level network isolation, `--network=host` is the standard approach on WSL2. > **Alternatively:** Use Podman via a Podman machine backend (the `podman-machine-default` setup). Podman machine handles networking using QEMU user networking, which does not depend on kernel nftables and works out of the box. ### CDI spec shows "Not Available" after Windows NVIDIA driver update When Windows NVIDIA drivers update, a new driver store directory appears at `/usr/lib/wsl/drivers/nvmdi.inf_amd64_/`. Any existing CDI spec pointing to the old hash becomes stale and causes: ``` crun: cannot stat '/usr/lib/wsl/drivers/nvmdi.inf_amd64_/libcuda.so.1.1': No such file or directory ``` **Fix:** regenerate the CDI spec in the distro where containers run: ```bash sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml ``` This picks up the current active driver hash automatically. --- ## Issue #1: Quick Install completes but distro doesn't appear in WSL list ### Symptoms - User clicks Quick Install for a distro (e.g., openSUSE-Leap-15.6) - UI shows "Installing from Microsoft..." then "Installed successfully!" - Distro does NOT appear in the app's distro list - `wsl --list` does not show the distro - No error message is displayed ### Root Cause The `wsl --install --no-launch` command installs the Windows Store (AppX) package but does **not** register the distro with WSL for some distributions. The distro needs to be launched at least once to complete WSL registration. **Technical details:** - The AppX package is installed successfully (visible via `Get-AppxPackage`) - The WSL registry entry (`HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss`) is NOT created - The `--no-launch` flag prevents the first-launch registration from occurring - Affected distros include openSUSE and potentially others that have custom first-boot setup ### Diagnosis 1. Check if AppX package is installed: ```powershell Get-AppxPackage -Name '*openSUSE*' | Select-Object Name, Version, Status ``` If this shows `Status: Ok`, the package is installed but not registered with WSL. 2. Check WSL registry for registered distros: ```cmd reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss" /s | findstr DistributionName ``` The problematic distro will be missing from this list. ### Solution **Fix applied in code:** Removed `--no-launch` flag from quick install. The distro now launches for first-time setup (user creation, password, etc.), which ensures proper WSL registration. **Manual workaround (if issue occurs):** 1. Remove the orphaned AppX package: ```powershell Get-AppxPackage '*openSUSE*Leap*15.6*' | Remove-AppxPackage ``` 2. Reinstall using `wsl --install` WITHOUT the `--no-launch` flag: ```cmd wsl --install openSUSE-Leap-15.6 ``` 3. Complete the first-time setup wizard that appears (create user, set password, etc.) ### Files Changed - `src-tauri/src/wsl/install.rs`: Changed `quick_install_distribution()` to use `no_launch: false` - Added `verify_distro_installed()` function to poll `wsl --list` and confirm registration ### Related - WSL GitHub Issues: This appears to be expected behavior for `--no-launch` with certain distros - Affects: openSUSE, potentially other distros with custom OOBE (Out-of-Box Experience) --- ## Issue #2: WSL is not responding / Operations timeout ### Symptoms - Actions like Start, Stop, or listing distros hang indefinitely - Error message appears: **"WSL is not responding. Try 'Force Restart WSL' to recover."** - The app becomes unresponsive when interacting with WSL - Commands eventually timeout after 30 seconds (default) ### Root Cause The WSL service or VM can become unresponsive due to: - **Terminal windows from newly installed distros** - If a distro was recently installed and the terminal window opened automatically during installation is still open, it can block WSL shutdown - A hung process inside a distro - WSL kernel crash or deadlock - Resource exhaustion (memory, disk) - Corrupted WSL state - Windows update pending restart ### Diagnosis 1. Check if WSL processes are running: ```powershell Get-Process -Name wsl*, vmwp* | Format-Table Name, Id, CPU, WorkingSet ``` 2. Try a simple WSL command from terminal: ```cmd wsl --list --verbose ``` If this hangs, WSL is definitely unresponsive. 3. Check Windows Event Viewer for WSL errors: - Event Viewer → Applications and Services Logs → Microsoft → Windows → WSL ### Solution **Quick fix:** If you recently installed a new distribution, close any terminal windows that were automatically opened during installation. These can prevent WSL from shutting down gracefully. **In-app solution:** Use the "Force Shutdown WSL" button that appears in the error banner. This runs `wsl --shutdown --force` to immediately terminate all WSL processes. **Manual recovery:** 1. Graceful shutdown: ```cmd wsl --shutdown ``` 2. If that hangs, force kill WSL processes: ```powershell # Kill WSL processes Stop-Process -Name "wsl" -Force -ErrorAction SilentlyContinue Stop-Process -Name "wslservice" -Force -ErrorAction SilentlyContinue # Kill the Hyper-V VM worker (if WSL2) Get-Process -Name "vmwp" -ErrorAction SilentlyContinue | Stop-Process -Force ``` 3. Restart WSL by launching any distro or running `wsl` **Adjusting timeouts:** If you frequently experience timeouts on slower systems, you can increase the timeout values in Settings → Timeouts. ### Files Changed - `src-tauri/src/wsl/executor/wsl_command/real.rs`: Timeout handling with configurable durations - `src-tauri/src/wsl/core.rs`: `force_kill_wsl()` implementation - `src-tauri/src/settings.rs`: `WslTimeoutConfig` for user-configurable timeouts ### Related - Default timeouts: Quick (10s), Default (30s), Long operations (600s) - Settings path: `%LOCALAPPDATA%\wsl-ui\settings.json` --- ## Issue #3: WSL is not installed ### Symptoms - App fails to start or shows error on launch - Error message: **"WSL is not installed"** - No distributions are listed - All WSL operations fail ### Root Cause WSL (Windows Subsystem for Linux) is not enabled or installed on the system. This is common on: - Fresh Windows installations - Systems where WSL was never enabled - After certain Windows resets ### Diagnosis 1. Check if WSL is available: ```cmd wsl --version ``` If this returns "not recognized" or an error, WSL is not installed. 2. Check Windows features: ```powershell Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux ``` ### Solution **Install WSL (Windows 10 version 2004+ or Windows 11):** 1. Open PowerShell or Command Prompt as **Administrator** 2. Run the install command: ```cmd wsl --install ``` 3. **Restart your computer** when prompted 4. After restart, WSL will complete setup. You may be prompted to create a Linux user account. **For older Windows 10 versions (pre-2004):** 1. Enable WSL feature: ```powershell dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart ``` 2. Enable Virtual Machine Platform (for WSL2): ```powershell dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` 3. Restart your computer 4. Download and install the WSL2 kernel update from Microsoft: https://aka.ms/wsl2kernel 5. Set WSL2 as default: ```cmd wsl --set-default-version 2 ``` ### Files Changed - `src/utils/errors.ts`: Error detection for "WSL not installed" scenario - Provides user-friendly hint: "Run wsl --install in PowerShell as administrator" ### Related - Microsoft WSL documentation: https://learn.microsoft.com/en-us/windows/wsl/install - Minimum requirements: Windows 10 version 1903+ (build 18362+) for WSL2 - Windows 11 has WSL available by default (but may need enabling) --- ## Issue #4: Files left behind after unregistering a WSL distribution ### Symptoms - After running `wsl --unregister `, files may still exist on disk - Orphaned folders in custom install locations - Disk space not fully reclaimed after removing a distro - Old distro folders visible in File Explorer ### Root Cause When you run `wsl --unregister `, WSL removes the registration and deletes the virtual disk (ext4.vhdx) at the registered path. However, files can be left behind in these scenarios: 1. **Wrong uninstall order** - If you uninstall the Microsoft Store app before running `--unregister`, the filesystem may become orphaned 2. **Custom install locations** - Distros imported with `wsl --import` to a custom path may leave the parent folder behind 3. **Store app remnants** - AppX packages keep files in `%LOCALAPPDATA%\Packages` ### Diagnosis 1. Check for remaining WSL filesystems: - Open File Explorer and look in `\\wsl$\` sidebar - the distro should no longer appear 2. Check for orphaned folders in common locations: ```powershell # Default WSL storage location dir "$env:LOCALAPPDATA\Packages" | Where-Object { $_.Name -like '*Linux*' -or $_.Name -like '*Ubuntu*' -or $_.Name -like '*SUSE*' -or $_.Name -like '*Debian*' } # Check custom import locations (if applicable) # e.g., wherever you specified with wsl --import ``` 3. Verify the distro is fully unregistered: ```cmd wsl --list --all ``` ### Solution **Correct order of operations:** 1. First, unregister from WSL: ```cmd wsl --list --verbose wsl --unregister ``` 2. Then uninstall the Store app (if applicable): - Settings → Apps → Search for the distro name → Uninstall 3. Verify cleanup and manually remove leftovers: ```powershell # Check and remove orphaned package folders $packages = Get-ChildItem "$env:LOCALAPPDATA\Packages" | Where-Object { $_.Name -match 'Linux|Ubuntu|SUSE|Debian|Kali|Fedora' } $packages | ForEach-Object { Write-Host $_.FullName } # Manually delete if confirmed orphaned ``` 4. For custom-imported distros, manually delete the install folder: ```powershell # Example: if you imported to D:\WSL\MyDistro Remove-Item -Recurse -Force "D:\WSL\MyDistro" ``` **What `--unregister` removes:** - The distro registration from WSL - The ext4.vhdx virtual disk file - All user data, settings, and installed software inside the distro **What `--unregister` does NOT remove:** - The parent folder (if custom location) - Microsoft Store app package - AppX package data in `%LOCALAPPDATA%\Packages` ### Related - Microsoft WSL FAQ: https://learn.microsoft.com/en-us/windows/wsl/faq - Once unregistered, all data is permanently lost unless backed up first - Use `wsl --export ` to backup before unregistering --- ## Issue #5: Cannot pull images from private container registries ### Symptoms - Error when trying to install a container image: "unauthorized" or "authentication required" - Container images from private registries (e.g., GitLab Container Registry, private Docker Hub, AWS ECR) fail to download - Public images work fine, but private/authenticated images fail ### Root Cause The built-in OCI implementation only supports **public container registries**. It does not have access to credentials stored in Docker or Podman configuration files. Private registries require authentication, which the built-in implementation cannot provide. ### Solution **Use Docker or Podman as the container runtime:** 1. **Install Docker or Podman** on your Windows system if not already installed - Docker Desktop: https://www.docker.com/products/docker-desktop/ - Podman Desktop: https://podman-desktop.io/ 2. **Authenticate with your private registry** using the CLI: ```bash # For Docker docker login registry.example.com # For Podman podman login registry.example.com # For GitLab Container Registry docker login registry.gitlab.com # For AWS ECR aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com ``` 3. **Change the container runtime in WSL UI:** - Go to **Settings → Remote Sources** - Under **Container Runtime**, select **Docker** or **Podman** - This setting persists across sessions 4. **Install your private image** using the Container option in the New Distribution dialog ### How it works - **Built-in OCI**: Downloads image layers directly from the registry. Fast and lightweight, but no authentication support. - **Docker/Podman**: Uses the container runtime's credential store (`~/.docker/config.json` or similar). Supports all authentication methods the runtime supports. ### Settings location Container runtime preference is stored in: ``` %LOCALAPPDATA%\wsl-ui\settings.json ``` Under the `containerRuntime` field: - `"builtin"` - Built-in OCI (default) - `"docker"` - Use Docker CLI - `"podman"` - Use Podman CLI - `{"custom": "command"}` - Custom runtime ### Related - Docker credential helpers: https://docs.docker.com/engine/reference/commandline/login/#credential-helpers - Podman authentication: https://docs.podman.io/en/latest/markdown/podman-login.1.html - The runtime indicator is shown in the Container installation dialog --- ## Issue #6: E2E tests fail with "Origin header is not a valid URL" ### Symptoms - E2E tests fail immediately in the `beforeEach` hook - Error message: **"WebDriverError: javascript error: Origin header is not a valid URL"** - Or WebdriverIO reports `ECONNREFUSED` for `http://localhost:4444/session` - Or WebDriver reports **"no such window: target window already closed"** / **"web view not found"** after one or more tests have passed - All tests affected, not just specific ones - The error occurs when `resetMockState()` tries to invoke Tauri commands via `browser.execute()` ### Root Cause This is a WebDriver/tauri-driver infrastructure issue. The error occurs when: 1. `tauri-driver` is not installed or is not available on `PATH` 2. The tauri-driver or msedgedriver becomes stale or corrupted 3. There are leftover processes from previous test runs 4. The WebDriver session has CORS/origin validation issues with the Tauri webview 5. The test runner attached to an older application instance, or the debug binary was not rebuilt after Tauri capability changes ### Diagnosis 1. Check for leftover processes: ```powershell Get-Process -Name "wsl-ui", "tauri-driver", "msedgedriver" -ErrorAction SilentlyContinue ``` 2. Confirm `tauri-driver` is installed: ```powershell Get-Command tauri-driver tauri-driver --version ``` 3. Check if port 4444 is in use: ```cmd netstat -ano | findstr :4444 ``` ### Solution **Kill all related processes and retry:** If `tauri-driver` is missing, install it first: ```powershell cargo install tauri-driver --locked ``` 1. Kill any running app instances: ```cmd taskkill /F /IM wsl-ui.exe ``` 2. Kill tauri-driver and msedgedriver: ```cmd taskkill /F /IM tauri-driver.exe taskkill /F /IM msedgedriver.exe ``` 3. Wait a few seconds for ports to clear, then retry the tests: ```cmd npm run test:e2e:dev -- --spec src/test/e2e/specs/your-test.spec.ts ``` 4. If the driver reports that the target window closed, rebuild the debug binary before retrying. E2E tests use the debug executable: ```cmd npm run tauri:build:debug ``` Confirm that `src-tauri\target\debug\wsl-ui.exe` has a current modified time, then rerun the individual spec. **If the issue persists:** - Update msedgedriver to match your Edge browser version - Download from: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ - Place the executable in the project root ### Files Changed - N/A - This is an infrastructure/environment issue ### Related - tauri-driver documentation: https://github.com/nicksenger/tauri-driver - WebDriver protocol: https://www.w3.org/TR/webdriver/ - This affects all E2E tests that use `browser.execute()` to invoke Tauri commands --- ## Issue #7: "Failed to translate" warnings when opening terminal ### Symptoms - When opening a terminal for a distro, multiple warning lines appear before the shell prompt: ``` wsl: Failed to translate 'C:\WINDOWS\system32' wsl: Failed to translate 'C:\WINDOWS' wsl: Failed to translate 'C:\WINDOWS\System32\Wbem' wsl: Failed to translate 'C:\WINDOWS\System32\WindowsPowerShell\v1.0\' wsl: Failed to translate 'C:\WINDOWS\System32\OpenSSH\' wsl: Failed to translate 'C:\Users\...\AppData\Local\Microsoft\WindowsApps' ``` - The shell still works, but these warnings clutter the terminal output - Typically occurs after disabling drive mounting for a distro ### Root Cause This happens when **automount is disabled** but **Append Windows PATH** is still enabled for the distro. When `appendWindowsPath` is enabled (the default), WSL tries to add Windows PATH directories to the Linux PATH environment variable. To do this, it needs to translate Windows paths like `C:\WINDOWS\system32` to their Linux equivalents (e.g., `/mnt/c/WINDOWS/system32`). However, if automount is disabled, the Windows drives are not mounted at `/mnt/c`, so WSL cannot translate these paths and emits a warning for each one. ### Diagnosis 1. Check if automount is disabled for the distro: - In WSL UI: Open distro settings → look for "Automount" toggle (should be OFF) - Or check `/etc/wsl.conf` inside the distro: ```bash cat /etc/wsl.conf ``` Look for `[automount]` section with `enabled=false` 2. Check if appendWindowsPath is still enabled: - In WSL UI: Look for "Append Windows PATH" toggle (will be ON) - Or in `/etc/wsl.conf`: `[interop]` section, `appendWindowsPath` setting ### Solution **In WSL UI:** 1. Open the distro's settings (gear icon) 2. Find the **"Append Windows PATH"** toggle (near the mount settings) 3. Turn it **OFF** 4. Restart the distro for changes to take effect **Manual fix via wsl.conf:** 1. Edit `/etc/wsl.conf` inside the distro: ```bash sudo nano /etc/wsl.conf ``` 2. Add or modify the `[interop]` section: ```ini [interop] appendWindowsPath=false ``` 3. Restart the distro: ```cmd wsl --terminate ``` ### Why This Makes Sense When you disable automount, you're typically trying to isolate the Linux environment from Windows. The `appendWindowsPath` feature only works when Windows drives are mounted, so it should also be disabled for a clean, warning-free experience. ### Files Changed - N/A - This is a WSL configuration issue, not an app bug ### Related - WSL interop documentation: https://learn.microsoft.com/en-us/windows/wsl/wsl-config - The `[interop]` section in wsl.conf controls Windows/Linux integration features - `appendWindowsPath` defaults to `true` if not specified --- ## Issue #8: Permission denied when saving per-distribution settings (wsl.conf) ### Symptoms - Error when trying to save per-distribution settings: "Permission denied" - The distribution's `/etc/wsl.conf` file cannot be updated - Error appears in logs mentioning write permission failure ### Root Cause The `/etc/wsl.conf` file inside WSL distributions is typically owned by `root:root` with permissions `644` (read-only for non-root users). This is especially common with distributions installed from the Microsoft Store (e.g., Ubuntu). When the app attempts to write to this file, it needs root privileges inside the distribution. ### How WSL UI handles this The app writes to `/etc/wsl.conf` by running a command **as root** inside the distribution: ```bash wsl -d -u root -- sh -c "cat > /etc/wsl.conf << 'EOF' [automount] enabled=true ... EOF" ``` This approach: - Requires the distribution to be **running** (or will start it) - Uses `-u root` to execute with root privileges - Safely writes the content using a heredoc ### Why UNC path writing doesn't work An alternative approach would be writing via the Windows UNC path (`\\wsl$\Ubuntu\etc\wsl.conf`). However, this doesn't work for root-owned files because: - UNC path access maps to the distribution's **default Linux user** (not root) - Linux file permissions still apply - There's no "sudo" equivalent for UNC path access ### Requirements - The distribution must be **running** to save settings - The app will execute a command as root inside the distro ### Solution If you see permission errors: 1. **Ensure the distribution is running** before saving settings - Start the distribution from the main list if it's stopped 2. **Manual workaround** (if the app fails): ```bash # Open a terminal in the distribution wsl -d # Edit wsl.conf as root sudo nano /etc/wsl.conf # Save and restart the distro exit wsl --terminate ``` 3. **Check file ownership** inside the distro: ```bash ls -la /etc/wsl.conf # Should show: -rw-r--r-- 1 root root ... /etc/wsl.conf ``` ### Files Changed - `src-tauri/src/settings.rs`: `write_wsl_conf()` now uses `wsl_executor().exec_as_root()` - `src-tauri/src/wsl/executor/wsl_command/mod.rs`: Added `exec_as_root` trait method - `src-tauri/src/wsl/executor/wsl_command/real.rs`: Implements `-u root` flag for root execution ### Related - WSL wsl.conf documentation: https://learn.microsoft.com/en-us/windows/wsl/wsl-config#wslconf - The `/etc/wsl.conf` file controls per-distribution settings like automount, network, interop, and boot options --- ## Issue #9: RDP session disconnects immediately ### Symptoms - You click the Remote Desktop button for a distro with xrdp running - The RDP connection opens but disconnects within seconds - The distro appears to shut down shortly after connecting - A keep-alive terminal opens automatically (this is expected behavior) ### Root Cause WSL has default timeout settings that automatically shut down idle distributions: - **instanceIdleTimeout**: Shuts down a distro after 15 seconds of no active console connections (default in recent WSL versions) - **vmIdleTimeout**: Shuts down the entire WSL VM after all distros are idle When you connect via RDP, there's no terminal/console session keeping the distro alive. Without the timeout settings configured, WSL considers the distro "idle" and shuts it down, even though you're actively using the graphical desktop. ### Diagnosis 1. Check your `.wslconfig` file: ```powershell notepad $env:USERPROFILE\.wslconfig ``` 2. Look for these settings (they should NOT be commented out with `#`): ```ini [general] instanceIdleTimeout=-1 [wsl2] vmIdleTimeout=-1 ``` 3. If these lines are missing or commented out, that's the issue. ### Solution **Option 1: Configure WSL timeouts (recommended)** Add these settings to your `.wslconfig` file to disable the idle timeouts: 1. Open or create the file: ```powershell notepad $env:USERPROFILE\.wslconfig ``` 2. Add or uncomment these lines: ```ini [general] instanceIdleTimeout=-1 [wsl2] vmIdleTimeout=-1 ``` 3. Restart WSL for changes to take effect: ```cmd wsl --shutdown ``` 4. The Remote Desktop button will now connect without needing a keep-alive terminal. **Option 2: Keep the terminal open (automatic workaround)** If you don't want to modify your `.wslconfig`, the app automatically opens a keep-alive terminal when it detects timeouts aren't configured. Simply leave this terminal open during your RDP session. When you're done with RDP, you can close the terminal. ### Why -1? Setting the timeout values to `-1` disables the timeout entirely. You can also set specific values in milliseconds if you prefer a longer timeout rather than disabling it completely: - `instanceIdleTimeout=300000` = 5 minutes - `vmIdleTimeout=600000` = 10 minutes ### Files Changed - `src-tauri/src/commands.rs`: `check_wsl_config_timeouts()` function checks for these settings - `src/store/distroStore.ts`: Opens keep-alive terminal when timeouts not configured ### Related - WSL configuration documentation: https://learn.microsoft.com/en-us/windows/wsl/wsl-config#wslconfig - The `.wslconfig` file is located at `%USERPROFILE%\.wslconfig` - These settings apply globally to all WSL distributions --- ## Issue #10: RDP connection fails or connects to wrong distro ### Symptoms - Clicking Remote Desktop connects to a different distro than expected - RDP connection fails with "unable to connect" error - xrdp appears to be running but RDP won't connect - Only one of multiple distros with xrdp works ### Root Cause Multiple WSL distributions are configured to use the same xrdp port (typically 3389 or 3390). Since all WSL2 distros share the same network namespace and localhost, only one distro can bind to a given port at a time. When two distros both have xrdp configured on port 3390: - Whichever distro starts xrdp **first** claims the port - The second distro's xrdp fails to start (port already in use) - Connecting to `localhost:3390` always reaches the first distro ### Diagnosis 1. Check which process owns the port from within WSL: ```bash # Run this in the distro you expect to be using the port sudo ss -tlnp | grep 3390 ``` 2. Check xrdp configuration in each distro: ```bash grep "^port=" /etc/xrdp/xrdp.ini ``` 3. If multiple distros show the same port, that's the conflict. ### Solution **Assign unique ports to each distro's xrdp:** 1. Edit `/etc/xrdp/xrdp.ini` in each distro: ```bash sudo nano /etc/xrdp/xrdp.ini ``` 2. Change the `port=` line to a unique value for each distro: ```ini ; Distro 1 (e.g., Ubuntu) port=3390 ; Distro 2 (e.g., Kali) port=3391 ; Distro 3 (e.g., Debian) port=3392 ``` 3. Restart xrdp in each distro: ```bash sudo service xrdp restart ``` 4. WSL UI will automatically detect the correct port for each distro when you click the Remote Desktop button. ### Suggested port allocation | Distro | Port | |--------|------| | Primary distro | 3390 | | Secondary distro | 3391 | | Third distro | 3392 | | ... | 3393+ | Avoid using port 3389 as it's the Windows default RDP port and may conflict with Windows Remote Desktop services. ### Files Changed - N/A - This is a configuration issue in the individual distros ### Related - xrdp configuration: The `/etc/xrdp/xrdp.ini` file controls xrdp settings - WSL UI reads the port from each distro's xrdp.ini to connect to the correct port - All WSL2 distros share localhost - this is by design in WSL2's networking model --- ## Issue #11: WSL System Shell fails with GUI_APPLICATIONS_DISABLED ### Symptoms - Clicking the system terminal icon (in the header) opens a terminal that immediately shows an error - Error message: ``` GUI application support is disabled via C:\Users\\.wslconfig or /etc/wsl.conf. Error code: Wsl/Service/CreateInstance/WSL_E_GUI_APPLICATIONS_DISABLED [process exited with code 4294967295 (0xffffffff)] ``` - The "SYS" and "VER" info box in the header may not appear (when system distro is unavailable) ### Root Cause The WSL2 "system distro" (CBL-Mariner/Azure Linux) is not running because GUI application support has been disabled. This happens when: 1. **`guiApplications=false`** is set in `%USERPROFILE%\.wslconfig`: ```ini [wsl2] guiApplications=false ``` 2. Running **WSL 1** only - the system distro requires WSL 2 3. Using an **older WSL version** without WSLg support (pre-Store inbox version) The system distro hosts WSLg (X/Wayland servers, PulseAudio) for GUI application support. When disabled, `wsl --system` commands fail with this error. ### Diagnosis 1. Check your `.wslconfig` file: ```powershell notepad $env:USERPROFILE\.wslconfig ``` Look for `guiApplications=false` under `[wsl2]` 2. Check WSL version: ```cmd wsl --version ``` WSLg requires WSL version 2.0+ from the Microsoft Store 3. Verify WSL 2 is available: ```cmd wsl --list --verbose ``` Check the VERSION column - WSL 1 distros won't have system distro access ### Solution **If you intentionally disabled GUI applications:** This is expected behavior. The system shell is only available when the system distro (WSLg) is running. You can: - Use a regular distro terminal instead of the system shell - The app will hide the SYS/VER info box when the system distro is unavailable **If you want to enable GUI application support:** 1. Edit your `.wslconfig`: ```powershell notepad $env:USERPROFILE\.wslconfig ``` 2. Either remove the `guiApplications=false` line or set it to `true`: ```ini [wsl2] guiApplications=true ``` 3. Restart WSL for changes to take effect: ```cmd wsl --shutdown ``` **If you need to update WSL:** ```cmd wsl --update ``` Or install from the Microsoft Store for the latest features. ### Why disable GUI applications? Users may intentionally disable GUI applications to: - Reduce resource usage (the system distro uses ~200MB RAM) - Run WSL in headless/server environments - Avoid WSLg startup overhead when only using CLI applications ### Files Changed - `src-tauri/src/wsl/info.rs`: `get_system_distro_info()` now returns `None` when system distro unavailable - `src-tauri/src/commands.rs`: Updated return type to `Option` - `src/services/wslService.ts`: Updated TypeScript type to handle `null` - `src/components/Header.tsx`: Already handles `null` by hiding SYS/VER box ### Related - WSLg GitHub: https://github.com/microsoft/wslg - WSL configuration: https://learn.microsoft.com/en-us/windows/wsl/wsl-config - The system distro is based on CBL-Mariner (now Azure Linux) --- ## Issue #12: Language settings reset to English after restart ### Symptoms - User selects a non-English language (e.g., 简体中文, Español) - Language works correctly during the session - After closing and reopening the app, the language reverts to English - The `settings.json` file still has the correct `locale` value ### Root Cause On startup, the locale sync effect in `App.tsx` fired immediately with `DEFAULT_SETTINGS` (which has `locale: "auto"`) **before** `loadSettings()` had finished loading the real settings from Tauri. This caused: 1. `locale = "auto"` resolved to English (or browser default) 2. `localStorage.setItem("wsl-ui-language", "en")` overwrote the saved language preference 3. When the real settings loaded moments later, i18next LanguageDetector had already cached `"en"` in localStorage The fix gates the locale sync effect on the `hasLoaded` flag, so it only runs after `loadSettings()` completes. ### Diagnosis 1. Check the settings file for the correct locale value: ``` %LOCALAPPDATA%\wsl-ui\settings.json ``` Look for the `"locale"` field — it should contain the language code (e.g., `"zh-CN"`, `"es"`, `"fr"`) 2. Check application logs for locale sync messages: ``` %LOCALAPPDATA%\wsl-ui\logs\ ``` Look for `[App] Locale sync:` entries — these show the resolved locale values at startup 3. Check WebView2 localStorage (used by i18next LanguageDetector): ``` %LOCALAPPDATA%\wsl-ui\EBWebView\ ``` ### Solution **Fixed in v0.18.2** by gating the locale sync effect on `hasLoaded`: ```typescript useEffect(() => { if (!hasLoaded) return; // Don't sync until real settings are loaded // ... locale sync logic }, [hasLoaded, settings?.locale, i18n]); ``` **Workaround for v0.18.1:** Manually set the locale in the settings file: 1. Close the application 2. Open `%LOCALAPPDATA%\wsl-ui\settings.json` in a text editor 3. Set the `"locale"` field to your desired language code: ```json { "locale": "zh-CN" } ``` 4. Save the file and reopen the application 5. If the language still resets, also clear the WebView2 cache in `%LOCALAPPDATA%\wsl-ui\EBWebView\` ### Files Changed - `src/App.tsx`: Added `hasLoaded` guard to locale sync effect + debug logging - `src/store/settingsStore.ts`: Added `hasLoaded` flag to settings store + debug logging - `src/components/settings/LanguageSettings.tsx`: Added debug logging for language changes - `src/i18n/index.ts`: Added debug logging for lazy-loaded language bundles ### Related - GitHub Issue: #42 - All install methods (NSIS installer, MSI, portable) share the same config directory at `%LOCALAPPDATA%\wsl-ui\` — there is no conflict between install types - Enable debug logging in Settings → Logging for more detail in logs --- ## Issue #13: Garbled or incorrect text after reinstalling WSL UI ### Symptoms - UI text appears garbled, shows wrong language, or reverts to English after reinstalling WSL UI - Switching from the Microsoft Store version to the EXE installer (or vice versa) causes the issue - The problem persists even after uninstalling and reinstalling again - Language settings appear correct in the app settings, but the UI text does not match ### Root Cause WSL UI stores app state in `%LOCALAPPDATA%\wsl-ui\` in two forms that **survive uninstall**: 1. **Tauri persistent settings** — `%LOCALAPPDATA%\wsl-ui\settings.json`. Includes your saved language preference and other settings. 2. **WebView2 browser data** — `%LOCALAPPDATA%\wsl-ui\EBWebView`. Includes `localStorage`, which the i18n system uses to remember the active display language (`wsl-ui-language` key). When you reinstall WSL UI, neither location is cleared. If the previous install left stale or corrupted data (for example, a partially-saved language state from a crash), the new install will read it and behave unexpectedly. This is especially visible for non-English locales, where the language bundle is lazy-loaded at startup. If the stored language code is invalid or the bundle fails to load, the app falls back to English — but the settings page may still show the old language as "selected". ### Diagnosis 1. Check the stored language in settings: ```powershell type "$env:LOCALAPPDATA\wsl-ui\settings.json" ``` Look for the `locale` field. If it contains an invalid or unexpected language code, that is the likely cause. 2. Check the WebView2 localStorage data (optional, advanced): The `wsl-ui-language` key in localStorage can be inspected using Edge DevTools or by checking the LevelDB files in: ``` %LOCALAPPDATA%\wsl-ui\EBWebView\Default\Local Storage ``` ### Solution **Recommended: clear the app data before reinstalling** 1. Uninstall WSL UI 2. Delete the app data directory (covers both settings and WebView2 data): ```powershell Remove-Item -Recurse -Force "$env:LOCALAPPDATA\wsl-ui" ``` 3. Reinstall WSL UI **Quick fix (without reinstalling):** 1. Manually edit the settings file to reset the language: ```powershell notepad "$env:LOCALAPPDATA\wsl-ui\settings.json" ``` Find the `locale` field and set it to `"auto"` or a valid language code (e.g., `"zh-CN"`): ```json { "locale": "auto" } ``` Save the file and restart WSL UI. 2. Alternatively, reset the language setting inside the app: - Go to **Settings → Application → Language** - Select **Auto-detect** or your preferred language - Restart WSL UI to confirm it persists ### Valid language codes The following language codes are supported: | Code | Language | |------|----------| | `en` | English | | `zh-CN` | Chinese (Simplified) | | `zh-TW` | Chinese (Traditional) | | `ja` | Japanese | | `ko` | Korean | | `es` | Spanish | | `hi` | Hindi | | `fr` | French | | `de` | German | | `pt-BR` | Portuguese (Brazil) | | `ar` | Arabic | | `ru` | Russian | | `pl` | Polish | | `tr` | Turkish | | `it` | Italian | | `auto` | System language (auto-detect) | ### Files Changed - `docs/TROUBLESHOOTING.md`: Added this entry documenting the issue and cleanup steps ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/52 - App data location: `%LOCALAPPDATA%\wsl-ui\` --- ## Issue #14: New distribution dialog clips off-screen on small displays ### Symptoms - User clicks the "New" button (or any control opening the install dialog) on a display shorter than ~525px of available height - Dialog opens centered, but the close button at the top and/or the Cancel/Install action buttons at the bottom are clipped off-screen - Page does not scroll, so there is no way to reach the clipped controls - User reported "opens a new unscrollable window that makes it impossible to do anything" ### Root Cause `src/components/NewDistroDialog.tsx` set the dialog container to `h-[95vh] min-h-[500px]`. The `min-h-[500px]` floor forced the dialog taller than the viewport on short displays. Because the outer overlay used `flex items-center justify-center` with no `overflow-y-auto`, the over-sized dialog was centered and clipped equally at top and bottom, with no scrollable scrollbar to recover the hidden parts. Other dialogs in the codebase (`HelpDialog`, `DistroInfoDialog`, `QuickActionsMenu`, etc.) use `max-h-[NNvh]` only and so scaled correctly with the viewport. NewDistroDialog was the outlier. ### Diagnosis 1. Resize the app window so its content area is below ~525 px tall. 2. Open the install dialog. 3. Observe missing close button and missing footer action buttons. ### Solution **Fix applied in code (`src/components/NewDistroDialog.tsx:577-589`):** - Removed `h-[95vh]` (forced height) and `min-h-[500px]` (overflow floor). - Replaced with `max-h-[calc(100vh-2rem)]` so the dialog grows with content but never exceeds the viewport (minus 2rem of breathing room matching the new outer padding). - Added `overflow-y-auto py-4` to the outer `fixed inset-0` overlay as a safety net so that on extreme small heights (e.g. 200 px) the user can still scroll to reach the footer. ### Files Changed - `src/components/NewDistroDialog.tsx`: Outer overlay made scrollable with vertical padding; dialog uses `max-h-[calc(100vh-2rem)]` instead of `h-[95vh] min-h-[500px]`. ### Related - Original report: Paperclip issue OCT-941 --- ## Issue #15: Installation fails with `Wsl/ERROR_PATH_NOT_FOUND` when parent directory is missing ### Symptoms - Installing or importing a distribution to a path under `%LOCALAPPDATA%\wsl\...` (e.g. `C:\Users\\AppData\Local\wsl\arch-linux-current`) fails when the intermediate `wsl` directory does not already exist. - Error message: `Failed to execute WSL command: ... Wsl/ERROR_PATH_NOT_FOUND`. ### Root Cause `wsl --import` does not create intermediate parent directories. When the install location's parent does not exist, the call fails immediately with `Wsl/ERROR_PATH_NOT_FOUND` before the import begins. ### Diagnosis 1. Look at the install location reported in the failed install dialog. 2. Confirm the parent directory does not exist, e.g.: ```powershell Test-Path "$env:LOCALAPPDATA\wsl" ``` 3. If it returns `False`, this is the bug. ### Solution **Fix applied in code:** `import_distribution` / `import_distribution_with_version` now call `std::fs::create_dir_all` on the install location before invoking `wsl --import`, so any missing parent directories are created automatically. **Manual workaround (if running an older build):** Create the parent directory yourself, then retry the install: ```powershell New-Item -ItemType Directory -Force "$env:LOCALAPPDATA\wsl" | Out-Null ``` ### Files Changed - `src-tauri/src/wsl/import_export.rs`: Added `ensure_install_location_exists()` helper and call it from both import wrappers; added regression tests. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/86 - Internal: OCT-799 --- ## Issue #16: Re-launching the app spawns a new window while one is already minimised to the tray ### Symptoms - User has "Minimize to tray" enabled (or chose "Minimize" from the close-action dialog). - Closing the window hides it; tray icon remains as expected. - User then re-opens the app from a desktop shortcut, the Start menu, or a pinned taskbar icon (anything other than clicking the tray icon). - A *second* window appears, and a *second* tray icon is added next to the first. - Closing that new window minimises it too, so the user can end up with multiple "minimised" instances and multiple tray icons. ### Root Cause The app did not enforce single-instance behaviour. Each time the `wsl-ui.exe` binary is launched, Windows spawns a new process. Each process independently: - creates its own `main` webview window (`visible: true` in `tauri.conf.json`), - builds and registers its own system tray icon. Clicking the tray icon correctly calls `show_main_window` on whichever process owns that icon, but it does not deduplicate processes. Tauri does not enforce single-instance by default — it has to be opted into via `tauri-plugin-single-instance`. ### Diagnosis 1. Reproduce: enable "Minimize to tray", close the window, then double-click the desktop / Start menu shortcut. 2. Open Task Manager → Details. You will see multiple `wsl-ui.exe` processes — one per launch. 3. The notification area shows one tray icon per process. ### Solution Added the official `tauri-plugin-single-instance` plugin. When a second `wsl-ui.exe` launches, the secondary process's call to `tauri::Builder::default().plugin(...)` triggers the callback in the primary process and the secondary exits. The callback reuses the existing `show_main_window` helper, which on Windows performs `show()` → `unminimize()` → `set_focus()`, restoring the hidden/minimised primary window instead of creating a new one. ### Files Changed - `src-tauri/Cargo.toml`: added `tauri-plugin-single-instance = "2"` dependency. - `src-tauri/src/main.rs`: registered the plugin before any other plugin in `tauri::Builder::default()`; callback calls `show_main_window(app)`. ### Related - Tauri v2 single-instance docs: https://v2.tauri.app/plugin/single-instance/ - Paperclip issue: OCT-940 --- ## Issue #17: Pulling a container image fails with "Blob digest mismatch" or "Blob size mismatch" ### Symptoms - Creating a distro from an OCI/container image (`docker.io`, `ghcr.io`, a custom registry, etc.) fails part-way through with an error such as: - `Registry error: Blob digest mismatch for sha256:...: computed sha256:...` - `Registry error: Blob size mismatch for sha256:...: expected N bytes, got M bytes` - `Registry error: Unsupported digest algorithm '...': only sha256 is supported` - The import stops and no distribution is created. ### Root Cause Each OCI layer is content-addressable: the manifest declares an exact `sha256` digest and byte size for every layer. WSL-UI now verifies the bytes it downloads against those values before merging layers and handing them to `wsl --import`. An error means the layer that arrived on disk did **not** match what the manifest promised. Common causes: - A **truncated or corrupted download** (dropped connection, flaky network, a proxy that mangled the stream) — usually a size mismatch. - A registry served over insecure **`http://`** where a MITM or a poisoned cache substituted layer content — a digest mismatch. - A registry/mirror bug returning the wrong blob for a digest. - A digest that isn't SHA-256 (unsupported): the app rejects it rather than importing unverified bytes. This is a safety feature: importing an unverified layer could silently produce a broken distro or run tampered content as a "real" distribution. ### Diagnosis - Retry the pull. If a **size mismatch** disappears on retry, it was a transient/corrupt download. - If a **digest mismatch** persists — especially against an `http://` registry — treat the source as untrusted. Pull the same image over `https://` from the authoritative registry. - Verify the image reference is correct and the registry/mirror is healthy. ### Solution - Retry over a stable network; transient truncations resolve on a clean re-download (the partial file is deleted automatically, so nothing broken is left behind). - Prefer `https://` registries so content can't be tampered with in transit. - If the mismatch is reproducible from a trusted `https://` source, the image or registry itself is serving corrupt data — report it upstream. ### Files Changed - `src-tauri/src/oci/registry.rs`: `download_blob` streams the layer through a SHA-256 hasher, verifies the computed digest and byte count against the manifest, deletes the partial file and errors on mismatch; added `verify_digest` helper and manifest-digest verification in `get_manifest`. - `src-tauri/src/oci/image.rs`: passes the manifest layer `size` into `download_blob`. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/106 - Paperclip issue: OCT-1144 - OCI content-addressable storage / digest spec --- ## Issue #18: Garbled characters (mojibake) in WSL errors on non-English locales ### Symptoms - The "WSL UNAVAILABLE" panel (or other WSL command output) renders as mojibake on a non-English Windows locale (e.g. Chinese zh-CN). - Example: `WSL check failed: *❍✚¡ABⵙⓤ❍N BLⵙBBnBuⵙB ...` followed by a readable English line such as `Try running 'wsl --status' in PowerShell to diagnose.` - English (en-US) locales are unaffected. ### Root Cause `wsl.exe` emits its output as UTF-16 LE. `decode_wsl_output` (`crates/wsl-core/src/parser.rs`) detected UTF-16 LE with a heuristic that only inspected the first few byte pairs for the ASCII NUL pattern (high byte = `0x00`). That pattern only holds when the payload is ASCII. On non-English locales the localized text is CJK, whose UTF-16 LE code units have non-NUL high bytes, so detection failed, the bytes fell through to `String::from_utf8_lossy`, and the UTF-16 LE buffer was misread as UTF-8 → mojibake. ### Diagnosis - Reproduces only on non-English Windows display locales. - Confirmed by feeding localized CJK text encoded as UTF-16 LE through `decode_wsl_output` and observing garbled output before the fix. ### Solution Made `decode_wsl_output` locale-robust: - Honor an explicit UTF-16 LE BOM (`0xFF 0xFE`) and UTF-8 BOM (`0xEF 0xBB 0xBF`), stripping them. - Replaced the leading-bytes heuristic with a whole-buffer scan: UTF-8 text effectively never contains NUL bytes, whereas UTF-16 LE `wsl.exe` output always contains NUL high bytes for ASCII/control characters (spaces, digits, newlines, the ASCII fallback lines present even in localized errors). When NUL bytes are concentrated in odd positions, it is treated as UTF-16 LE regardless of locale. - Added an invalid-UTF-8 + even-length safety net that decodes as UTF-16 LE. ### Files Changed - `crates/wsl-core/src/parser.rs`: rewrote `decode_wsl_output` / `looks_like_utf16le`, added `decode_utf16le` helper, and added localized (CJK) UTF-16 LE regression tests. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/99 - Internal: OCT-1066. Prior encoding work (OCT-412, OCT-436) covered only the ASCII path; this is the non-ASCII-locale recurrence. --- ## Issue #19: Mounting a physical disk fails with "Invalid path" ### Symptoms - Status-bar "Mount disk" → physical disk tab → any physical drive fails with `Invalid path` before anything happens. - The same disk mounts fine from an elevated terminal with `wsl --mount \\.\PHYSICALDRIVE1`. ### Root Cause Physical drives are addressed via the Windows device namespace (`\\.\PHYSICALDRIVEn`). The app's file-path validation treated the `.` in `\\.\` as a path component ending in a dot (a Windows-reserved pattern) and rejected the path before running `wsl --mount`. ### Solution **Fix applied in code:** `mount_disk` now validates disk paths with `validate_disk_path`, which strictly accepts `\\.\PHYSICALDRIVE` device paths and applies the normal file-path validation to VHD/VHDX paths. **Workaround (older builds):** mount from an elevated terminal: `wsl --mount \\.\PHYSICALDRIVEn [--bare|--partition N ...]`. ### Files Changed - `src-tauri/src/validation.rs`: added `validate_disk_path` / `is_physical_drive_path` with tests. - `src-tauri/src/commands.rs`: `mount_disk` uses `validate_disk_path`. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/112 --- ## Issue #20: Default distribution starts by itself when the app opens or regains focus ### Symptoms - Opening WSL UI, navigating from Settings back to the dashboard, or focusing the window starts the *default* WSL distribution even though you never clicked Start. ### Root Cause The dashboard queried the WSL VM IP address and system-distro info via `wsl --system` **without a target distribution**. An untargeted `wsl --system` runs against the default distribution and boots it as a side effect. These queries fire when the header/distro list mounts, i.e. on app open and on every navigation back to the dashboard. ### Solution **Fix applied in code:** system-distro queries (`get_wsl_ip`, `get_system_distro_info`) now run only when a distribution is already running, and explicitly target it with `wsl --system -d `. When nothing is running they return "unknown" without executing `wsl.exe`, so a status query can never boot a distribution. ### Files Changed - `src-tauri/src/wsl/info.rs`: gate on a running distribution (default preferred). - `src-tauri/src/wsl/executor/wsl_command/{mod,real,mock}.rs`: `--system` calls take a target distribution. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/157 --- ## Issue #21: Black screen for ~20 seconds on startup (regions where Google is unreachable) ### Symptoms - The window opens immediately but stays black for ~20–25 seconds before the UI renders. - Only happens on networks where `fonts.googleapis.com` is blocked or unreachable (e.g. mainland China); startup is ~1s elsewhere. ### Root Cause The UI fonts (JetBrains Mono, Outfit) were imported from Google Fonts via a render-blocking CSS `@import`. When the host is unreachable, first render blocks until the request times out. ### Solution **Fix applied in code:** the woff2 font subsets (~113 KB) are bundled with the app (`public/fonts/` + `src/fonts.css`) and loaded with `font-display: swap`. Startup performs no network font request at all. ### Files Changed - `src/index.css`: local `@import './fonts.css'` replaces the Google Fonts URL. - `src/fonts.css`, `public/fonts/*.woff2`: self-hosted fonts. ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/158 --- ## Issue #22: Tauri build reports mismatched JavaScript and Rust package versions ### Symptoms - `npm run tauri:build:debug` stops before compiling the application. - The error says `Found version mismatched Tauri packages` and lists pairs such as `tauri` versus `@tauri-apps/api`, or Rust plugins versus their JavaScript packages. ### Root Cause The installed `node_modules` was resolved by a different package manager or from dependency ranges newer than the repository's npm lock file. Tauri requires corresponding JavaScript and Rust packages to use compatible major/minor releases. ### Diagnosis 1. Check whether the error references packages under `node_modules/.pnpm` even though the repository uses `package-lock.json` and npm in CI. 2. Compare the versions printed by the Tauri error with `src-tauri/Cargo.lock`. 3. Check for an unintended `pnpm-lock.yaml` or a locally installed dependency tree that does not match `package-lock.json`. ### Solution Restore the repository's npm-locked dependency tree: ```powershell npm ci npm run tauri:build:debug ``` Do not commit a second package-manager lock file unless the project is intentionally migrating package managers. After rebuilding, confirm the debug executable timestamp changed before running E2E tests. --- ## Issue #23: Quick Actions menu does not appear ### Symptoms - Clicking the Quick Actions button does not display the menu. - The application shows an "Action Failed" notification for Quick Actions. - Development logs mention a popup creation, permission, or event transport failure. ### Root Cause Quick Actions is rendered in a separate Tauri WebView. The menu can fail to open when the frontend bundle is stale, the Tauri capability changes have not been included in the native build, or popup event/window permissions are unavailable. ### Diagnosis 1. Confirm that `popup.html` is present in the Vite build output. 2. Rebuild the native debug executable with `npm run tauri:build:debug` after capability changes. 3. Confirm the debug executable timestamp changed after the rebuild. 4. Before E2E runs, terminate lingering Tauri driver and Edge driver processes so the new executable and capability configuration are used. 5. Check application logs for `usePopupWindow` or `PopupHost` errors. ### Solution Use a freshly rebuilt Tauri executable and ensure both the main-window and popup capability files are packaged. If the problem persists, restart the application to recreate the popup WebView and inspect the first logged transport or window error. ### Files Changed - `src-tauri/capabilities/default.json`: grants the main window popup-management permissions. - `src-tauri/capabilities/quick-actions-popup.json`: grants the popup only event permissions. - `src/hooks/usePopupWindow.ts`: reports popup lifecycle failures. --- ## Issue #24: Fixed drives no longer appear under /mnt after enabling virtiofs ### Symptoms - After adding `virtiofs=true` to the `[wsl2]` section of `.wslconfig`, fixed Windows drives (e.g. `/mnt/c`, `/mnt/d`) are no longer automounted inside distributions. - No error is shown — the mounts are silently absent. - Mounted disks may also not appear in WSL UI's mounted-disk list. ### Root Cause `virtiofs=true` is an opt-in WSL feature (2026) that replaces the default Plan 9 file sharing transport. In current WSL builds it silently skips automounting fixed drives (upstream bug), and virtiofs mounts do not appear as `/dev/sd*` devices, which is what WSL UI's mounted-disk discovery currently matches. ### Diagnosis 1. Check `%USERPROFILE%\.wslconfig` for `virtiofs=true` under `[wsl2]`. 2. Run `mount` inside the distro — with virtiofs the Windows drive mounts are missing or use a `virtiofs` filesystem type instead of `9p`. ### Solution Remove `virtiofs=true` (or set it to `false`) in `.wslconfig` and run `wsl --shutdown`, then restart the distribution. Track the upstream fix before re-enabling. ### Related - https://github.com/microsoft/WSL/issues/40773 (automount silently skipped) - https://github.com/microsoft/WSL/issues/40719 (file ownership anomalies on Windows bind mounts) - `docs/plans/2026-09-03-wsl-developments-review.md` (WSL 2026 developments review) --- ## Issue #25: "WSL command failed" error on non-English Windows with no installed distributions ### Symptoms - On a non-English Windows locale (e.g. Chinese zh-CN), WSL is installed but no distributions are registered, and the app shows an error banner `WSL command failed:` instead of the normal empty state. - Logs show repeated failures: ``` [wsl_ui::wsl::core][WARN] WSL list command failed: [ERROR] [distroStore.fetchDistros] Error: {"code":"UNKNOWN","message":"WSL command failed: "} ``` - The same zero-distro state works correctly on English Windows. ### Root Cause `wsl.exe --list --verbose` reports "no installed distributions" in the system language and exits with code 1. The app detected this valid empty state by matching the English phrase `no installed distributions` only, so localized output (e.g. zh-CN `适用于 Linux 的 Windows 子系统没有已安装的分发。`) fell through to the generic command-failure path. Same locale-fragility family as Issue #18 (UTF-16 LE mojibake), but a different code path: this one is English-only string matching for the empty-list case. ### Diagnosis 1. Run `wsl.exe --list --verbose` in a terminal; on an affected system it prints the localized no-distro message and exits with code 1. 2. Check `HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss` — no GUID subkeys confirms zero registered distributions. ### Solution Empty-state detection is now locale-independent: when the list command fails, the app consults the Lxss registry and treats "no registered distributions" as the valid empty state. The registry check is strict — if the Lxss key cannot be read (as opposed to being absent or having no distro entries), the original command error is surfaced instead of an empty list, so a transient registry failure never silently clears the distribution inventory. The phrase match was also extended with the confirmed zh-CN message as a fast path. ### Files Changed - `src-tauri/src/wsl/core.rs`: added `is_no_distros_output` (English + zh-CN phrases) and a registry-based empty-distro fallback in `list_distributions`, with regression tests. - `src-tauri/src/wsl/executor/resource/{mod,real,mock}.rs`: new `registry_confirms_no_distros` ResourceMonitor method that distinguishes "no registered distros" from "registry unreadable". ### Related - GitHub issue: https://github.com/octasoft-ltd/wsl-ui/issues/101 - Internal: OCT-1132. Related locale work: OCT-1066 (Issue #18, UTF-16 LE decoding). --- ## Issue #26: Wrong or missing error detection on non-English Windows (localized CLI output) ### Symptoms - On localized Windows (e.g. Chinese, German, Japanese): - A missing `wsl.exe` shows a generic "Unknown" preflight error instead of the "WSL is not installed" guidance screen. - Compacting a distro VHDX via diskpart could report success even though diskpart failed (or vice versa, failures carried no useful detail). - A missing Hyper-V `Optimize-VHD` cmdlet surfaced as a raw localized PowerShell error instead of the friendly "Hyper-V feature may not be installed" message. ### Root Cause Several code paths classified `wsl.exe`, diskpart, and PowerShell output by matching English-only substrings ("not found", "successfully compacted", "not recognized"). Windows localizes that text (FormatMessageW, localized diskpart/PowerShell messages), so the checks silently misfired on non-English systems. Same family as Issues about UTF-16 LE decoding (#99) and localized parsing (#101), but in paths those fixes did not cover. ### Diagnosis 1. Set Windows display language to a non-English locale (e.g. zh-CN). 2. Rename/remove `wsl.exe` from PATH and launch the app: preflight showed a generic error rather than the install guidance. 3. Run a VHDX compact where diskpart fails (e.g. distro still running): the app reported success because the localized failure text matched no pattern. ### Solution Classification now uses locale-independent signals first: - Preflight: WSL hex error codes (`0x8007019e`, `0x80370102`), the stable `(os error 2/3)` suffix Rust appends to spawn failures, and an on-disk existence check of the configured `wsl` executable (resolved against PATH). English phrases remain only as a fallback. - Diskpart compact: the elevated wrapper appends `WSLUI_DISKPART_EXIT=` to the captured output so diskpart's real exit code decides success, regardless of locale. - Optimize-VHD: the elevated wrapper writes the .NET exception type name (e.g. `System.Management.Automation.CommandNotFoundException`), which is locale-invariant. ### Files Changed - `src-tauri/src/wsl/executor/wsl_command/real.rs`: locale-independent preflight failure classification + regression tests with zh-CN/de-DE messages. - `src-tauri/src/wsl/executor/resource/real.rs`: diskpart exit-code marker, exception-type-based Optimize-VHD detection + localized regression tests. ### Related - GitHub issue #102 (OCT-1136); related #99 (OCT-1066), #101 (OCT-1132) --- ## Issue #27: Leftover temp tar files and dangling containers after a failed container-image install ### Symptoms - Installing a distribution from a container image fails with "Failed to create install directory" (e.g. the chosen install location is on a full disk, a disconnected drive, or a protected path). - Large `wsl-image-.tar` files or `wsl-oci-` folders accumulate in `%TEMP%`. - `docker ps -a` / `podman ps -a` shows unnamed stopped containers left over from failed installs, one per retry. ### Root Cause The install flow pulled and exported the container image *before* validating the install location. When creating the install directory failed, the function returned early and skipped the cleanup calls that remove the exported tar (and the intermediate container in the Docker/Podman path). Every retry repeated the expensive work and leaked another set of artifacts. ### Diagnosis 1. Check `%TEMP%` for `wsl-image-*.tar` files or `wsl-oci-*` directories. 2. Run `docker ps -a` or `podman ps -a` and look for stopped containers created around the time of the failed installs. ### Solution Fixed by creating the install directory *before* the expensive pull/export work (a bad location now fails fast, before anything is downloaded), and by guarding the temp tar / OCI work directory with an RAII `TempFileGuard` so it is removed on every exit path, including panics. Temp artifact names now include a per-install sequence number (`wsl-image--.tar`, `wsl-oci--`) so two installs running at the same time cannot overwrite or delete each other's artifacts. For artifacts leaked by older versions: delete `wsl-image-*.tar` / `wsl-oci-*` from `%TEMP%` and remove leftover containers with `docker rm ` / `podman rm `. ### Files Changed - `src-tauri/src/wsl/install.rs`: install location created up front; temp artifacts guarded. - `src-tauri/src/temp_file_guard.rs`: `TempFileGuard` extended to clean up directories. ### Related - GitHub issue #103 (OCT-1138) --- ## Issue #28: Quick Install is empty or stays empty after a failed catalog request ### Symptoms The Quick Install list is empty even though `wsl --list --online` succeeds in PowerShell, or the list never recovers after a network/WSL error. ### Root Cause The dialog previously hid catalog errors and could retain an empty list after loading its separate metadata catalog. The backend also accepted unsuccessful WSL command output as an empty successful result. ### Diagnosis and Solution Open the dialog again or choose **Retry**. The dialog now refreshes the catalog and displays WSL's native error details. Run `wsl --list --online` in PowerShell if an error persists; record the returned message rather than assuming the network is blocked. Responses from a closed dialog are ignored. Related: [GitHub #154](https://github.com/octasoft-ltd/wsl-ui/issues/154). ## Issue #29: Download remains at “0 B of ?” Servers can stream a file without a Content-Length header. This previously suppressed progress events. Downloads now report bytes periodically and flush the final byte count even when a percentage cannot be calculated. An unknown total alone does not indicate a stalled download. Related: [GitHub #145](https://github.com/octasoft-ltd/wsl-ui/issues/145). ## Issue #30: Bare-mounted disks disappear from the application A bare attachment has no mounted filesystem, so filesystem discovery can legitimately return no paths. The app now retains the disk attachment separately, displays it without an invented mount path, and unmounts it by its original disk path. Successful WSL shutdown clears attachment tracking. If WSL was shut down outside the app, stale tracking can still need reconciliation; an empty filesystem list alone cannot prove a disk was detached. Partition mounts now preserve their supplied name, filesystem and options. Discovery recognizes device names beyond `/dev/sd*` and filters out unrelated system/container mounts. For filesystem mounts with no name supplied, the app requests a unique `wsl-ui-...` name under `/mnt/wsl`. This gives discovery and attachment tracking the same exact path, so the disk appears once and its unmount button uses the correct Windows path. Supply a mount name if you need a predictable Linux path. Bare attachments remain unnamed. Related: [#112](https://github.com/octasoft-ltd/wsl-ui/issues/112), [#130](https://github.com/octasoft-ltd/wsl-ui/issues/130), [#116](https://github.com/octasoft-ltd/wsl-ui/issues/116), [#165](https://github.com/octasoft-ltd/wsl-ui/issues/165). ## Issue #31: Startup sudo actions fail without a usable password prompt Background startup actions cannot collect a sudo password. The app attempts them with noninteractive sudo, allowing passwordless policies or usable cached credentials. If sudo requires a password, it fails promptly and the notification directs you to **Quick Actions**, where the password prompt is available. Actions configured to run in a terminal now open that terminal on startup. Hidden-output failures and terminal-launch failures produce notifications. Related: [GitHub #119](https://github.com/octasoft-ltd/wsl-ui/issues/119). ## Issue #32: Long disk operations stop after ten minutes Older versions applied a hard timeout to conversions, moves, imports and exports, then suggested force-restarting WSL. These disk-writing operations now wait for the command to finish. Install, resize and sparse operations use the same policy. Ordinary status queries retain timeouts. A large operation taking longer than ten minutes is not itself evidence that WSL is hung; avoid force shutdown while it is writing a distribution or archive. Related: [GitHub #151](https://github.com/octasoft-ltd/wsl-ui/issues/151). --- ## Issue #33: Built-in download fails while resolving or checking its checksum Built-in downloads now require a SHA-256 entry from the publisher's checksum manifest before importing an image. A checksum fetch, format or mismatch error stops the install. Retry if the publisher is updating a moving release; do not import the failed download. If a built-in URL was customized, supply an explicit SHA-256 checksum for that exact image. Custom catalog entries can still provide their own hash. Related: [GitHub #110](https://github.com/octasoft-ltd/wsl-ui/issues/110). --- ## Issue #34: Windows ARM64 build cannot find the native C compiler ### Symptoms A local `cargo build --locked --target aarch64-pc-windows-msvc` fails while compiling `aws-lc-sys`. The observed error was: ```text error occurred in cc-rs: failed to find tool "cl.exe": program not found (see https://docs.rs/cc/latest/cc/#compile-time-requirements for help) ``` ### Root Cause The reqwest 0.13 TLS dependency uses AWS-LC. Its Windows ARM64 build needs the native ARM64 build tools and `clang-cl`, in addition to Rust's ARM64 target. Installing the Rust target alone does not install those Visual Studio components. An x64 build can succeed while the ARM64 toolchain is incomplete. ### Solution In Visual Studio Installer, modify the C++ build tools installation to include the ARM64 MSVC build tools and Windows SDK. Also enable **C++ Clang Compiler for Windows** and **MSBuild support for LLVM (clang-cl) toolset**, as required by [AWS-LC's Windows build instructions](https://aws.github.io/aws-lc-rs/requirements/windows). Restart the developer shell, add the Rust target with `rustup target add aarch64-pc-windows-msvc`, and retry the command from `src-tauri`. The `rust-tests` CI job now builds the ARM64 app after the Rust tests on `windows-latest`. This checks compilation without launching or publishing the app; an ARM64 compile check does not exercise the app on an ARM64 device. ### Files Changed - `.github/workflows/e2e.yml`: install the ARM64 Rust target and compile the app after the existing app and core tests. --- ## Template for New Issues ```markdown ## Issue #N: [Brief description] ### Symptoms - What the user observes - Error messages (if any) - Expected vs actual behavior ### Root Cause Technical explanation of why this happens. ### Diagnosis Steps to confirm/diagnose the issue. ### Solution How we fixed it or workaround steps. ### Files Changed - List of files modified to fix the issue ### Related - Links to external issues, documentation, etc. ``` ## New distribution name rejected by WSL If registration fails with `Invalid distribution name` and `Wsl/Service/RegisterDistro/E_INVALIDARG`, use ASCII letters, numbers, dots, underscores or hyphens in the new name. For example, use `Rocky-Test` instead of `Rocky Test`. Quoting a name does not make spaces valid for registration. Microsoft's [`s_ValidateDistroName` implementation](https://github.com/microsoft/WSL/blob/master/src/windows/service/exe/LxssUserSession.cpp) checks this character restriction. WSL UI validates new names before starting imports or downloads. Handling an existing externally assigned name is a separate compatibility concern; a parser accepting spaces does not establish that WSL can register a new name containing them.