---
name: hermes-desktop-windows
description: Diagnose and troubleshoot Hermes Desktop (Electron app) on Windows. Use when the user reports "Update didn't finish / Backend update failed" dialogs, plugin startup failures (telegram/Discord/Slack), Desktop hangs at startup, gateway connection problems, or general Windows-side Hermes oddities. Covers log locations, process investigation, the misleading "backend update failed" error, and PowerShell patterns that work reliably with this user's terminal.
---

# Hermes Desktop on Windows

Hermes Desktop is the Electron chat GUI that ships as `<arch>-unpacked/Hermes.exe` and is installed under `%LOCALAPPDATA%\hermes\hermes-agent\apps\desktop\release\`. The Python backend (agent + plugins) runs as a child process and is what the updater means by "backend."

**Arch-specific release folder — probe with a wildcard, never a hardcoded arch.** The folder name is CPU-dependent: `win-unpacked` on x64 (Connie) vs `win-arm64-unpacked` on ARM64 (Surface tablet). A version probe against the literal `win-unpacked\Hermes.exe` returns `Cannot find path` on ARM64 and looks like a broken/missing install when it isn't. Use the wildcard form for any version/path probe:

```powershell
Get-ChildItem "$env:LOCALAPPDATA\hermes\hermes-agent\apps\desktop\release\*-unpacked\Hermes.exe" | Select-Object FullName, @{n='Version';e={$_.VersionInfo.ProductVersion}}
```

If even the wildcard finds nothing, next suspects: `%LOCALAPPDATA%\Programs\hermes*` (per-user installer layouts) and the Start Menu shortcut's target (right-click → Open file location). Also: a broad `-Recurse -Filter Hermes.exe` across the whole `hermes` tree will also hit the venv CLI shim (`...\venv\Scripts\hermes.exe`) — that's the CLI, not the Desktop app; read the path before concluding.

**Two backends, one nag.** A "backend needs an update" warning inside Desktop can mean the app's *bundled* backend OR the *remote* backend it's connected to. When the device is a pure client of the VPS gateway (Remote gateway mode), verify the VPS side first (`/root/.hermes/venv/bin/hermes --version`). If the VPS is current, the nag is about the bundled copy — cosmetic for remote-mode use; fix by updating the Desktop app itself, never the VPS. (Seen on the Surface tablet 2026-07-19: nag fired while VPS + local venv were both 0.18.2, i.e. latest.)

## When to load
- User sees "Update didn't finish — Backend update failed" dialog
- User reports Desktop hangs, won't connect to gateway, or shows partial UI
- `errors.log` shows plugin failures (telegram, discord, slack)
- User asks where Desktop stores logs, config, or data on Windows
- Multiple Hermes processes behaving oddly
- User wants to know whether to apply a Desktop update
- User reports `+` button / file picker error with `\\wsl.localhost\Ubuntu\root` "not accessible" (0.20.0 regression, rollback to `win-unpacked.bak`)
- User sees blank screen with blinking cursor on launch (missing/broken venv; check both `venv\` and `env\` folder names)
- User reports they cleaned up APPDATA while OneDrive was active
- User reports installer stuck at "Installing Node.js dependencies" or similar mid-install step (rebuild missing files manually instead)

## Critical log locations
The Desktop writes logs to **`%LOCALAPPDATA%\hermes\logs\`** (plural "hermes" at LOCALAPPDATA root, NOT under the `hermes-agent` subdirectory). Default full path: `C:\Users\<user>\AppData\Local\hermes\logs\`.

Files in priority order for diagnosis:
- `errors.log` — plugin/platform failures, unhandled exceptions (the BIG one)
- `agent.log` — agent loop trace
- `gateway.log` — gateway connection attempts to VPS
- `desktop.log` — Electron-side UI events

Previous-day rotation: `errors.log.1` exists alongside.

## "Backend update failed" is a red herring
When the Desktop updater reports "Update didn't finish — Backend update failed," **the update itself usually succeeded.** The Desktop is reporting that the post-update backend (local Python agent + plugins) did not come up healthy.

Most common cause: a platform plugin is wedged on startup. Classic signature in `errors.log`:
- `telegram.error.TimedOut`
- `Conflict: terminated by other getUpdates request`
- `Updater not running 60s after reconnect — treating as wedged`
- Repeated `polling conflict (N/5)` warnings climbing through retries

This is especially common right after a Desktop restart, when a previous bot instance still holds a session on the platform's servers. The platform's session timeout is usually 5–10 minutes.

## Code blocks must be valid against the user's filesystem, not against your view of it
Three failure modes bit during a 2026-08-12 debug of the Hermes Desktop `+` picker (UNC path `\\wsl.localhost\Ubuntu\root` "not accessible"). Each one is a class to remember:

**1. Path-string corruption by upstream redaction.** When a Windows command returns a path containing segments that look like user-identity or hostname strings, a chat-surface filter can rewrite them to bracketed placeholders (`[PERSON_NAME]`, `[ADDRESS]`) before they reach you. The user sees the real path; you see a corrupted one. Symptom: you confidently paste a code block referencing `$env:LOCALAPPDATA\hermes-agent`, only to discover the real directory is `$env:LOCALAPPDATA\hermes` (no `-agent` suffix). **Rule: confirm every Windows path/env-var you write in a code block against a layout the user has already verified exists.** When in doubt, ask before pasting — don't ship a code block you haven't sanity-checked. The user's flag of "[PERSON_NAME] in a bunch of your answers" was the diagnostic; treat any redaction-shaped string in a path as a bug in your output, not a feature of the data.

**2. Cross-shell awareness.** The agent's terminal is the VPS ([ADDRESS]), but diagnostic commands for Windows-side issues are Windows-only. `powershell: command not found` is the tell. **Rule: any PowerShell block you produce for a Windows-side diagnostic is paste-on-Windows material — never run it from your own terminal.** If a step requires you to verify something on the VPS side, separate it explicitly and only run that piece yourself.

**`\\wsl.localhost\Ubuntu\root` is always `UnauthorizedAccessException` from Windows — not a permissions bug, not a stopped distro.** WSL2's 9P file share exports `/` and `/root` read-only to Windows by design. No `wsl --shutdown` / `cmdkey` / distro re-registration will unlock it. If Hermes Desktop's working directory is `/root/...`, the native Open Folder picker will always fail. The fix is to **move the cwd off the [ADDRESS] path entirely** (e.g. to `%USERPROFILE%\.hermes`), not to chase the WSL bridge. Workaround until fixed: drag-and-drop from File Explorer — it uses a native Windows code path and bypasses the UNC bridge entirely.

**Regression flag (2026-08-12).** This failure was NOT present before 0.20.0. It appeared immediately after the 0.20.0 install (desktop-build-stamp `2026-08-12T02:51`, processes started 10:48 PM Denver time). The previous working build is preserved at `win-unpacked.bak` in the release folder. If a user reports this and is on 0.20.0, offer a rollback before any other fix.

**Rollback procedure (0.20.0 regression):**
1. Fully quit Hermes Desktop (system tray → Quit, verify no Hermes.exe in Task Manager).
2. Run in ADMIN PowerShell:
   ```
   Rename-Item "$env:LOCALAPPDATA\hermes\hermes-agent\apps\desktop\release\win-unpacked" "win-unpacked.new"
   Rename-Item "$env:LOCALAPPDATA\hermes\hermes-agent\apps\desktop\release\win-unpacked.bak" "win-unpacked"
   ```
3. Relaunch Hermes Desktop and test the `+` button.

If rollback resolves the picker, 0.20.0 is confirmed as the regression source. Hold off on re-updating until the devs ship a fix (check release notes for "file picker", "WSL", or "open folder").

**Probe order for any `\\wsl.localhost` UNC failure:**
1. `wsl -d <distro> -- echo ready` — confirms distro is responsive (not the same as the UNC being reachable).
2. `Test-Path \\wsl.localhost\<distro>\<path>` — returns `False` with `UnauthorizedAccessException` for `/root` and `/` even when the distro is running, which proves it's the export policy, not the distro state. Stop probing WSL at that point; the answer is to move the cwd.

## Debugging playbook
1. **Confirm Hermes is actually running** (multiple `Hermes.exe` is normal — main, renderer, GPU, utility. Never suggest killing them all.):
   ```
   Get-Process | Where-Object {$_.Name -like "*hermes*"} | Select-Object Name, Id, StartTime | Format-Table -AutoSize
   ```

2. **Tail the errors log for the actual failure**:
   ```
   Get-Content "$env:LOCALAPPDATA\hermes\logs\errors.log" -Tail 80 -ErrorAction SilentlyContinue
   ```

3. **Search for plugin-specific errors**:
   ```
   Get-Content "$env:LOCALAPPDATA\hermes\logs\errors.log" -Tail 200 | Select-String -Pattern "polling|update|Conflict|Telegram|Discord|Slack"
   ```

4. **Identify the wedged plugin** from WARNING lines, then choose:
   - **Wait** for the platform's session timeout (5–10 min) — least invasive
   - **Disable the plugin in config** if the user doesn't actively use it
   - **Clean Desktop restart** (X button, check system tray, quit fully, reopen) for a clean slate

5. **Re-attempt the update** from the Desktop updater UI.

## Clean reinstall when venv is gone and rollback didn't help
If `win-unpacked.bak` rollback doesn't fix things, or if the `.bak` folder is missing, the venv itself may be corrupted or deleted. Symptom: blank screen with blinking cursor, and `$env:LOCALAPPDATA\hermes\venv\Scripts\python.exe` returns "venv missing." The unpacked Electron app is NOT in Add/Remove Programs — uninstall = manual folder delete.

**Procedure:**
1. Kill all Hermes processes and verify none remain.
2. Delete BOTH folders: `$env:LOCALAPPDATA\hermes` AND `$env:APPDATA\Hermes`.
3. Reinstall from the installer package.
4. Reconnect to Remote gateway (URL + token).
5. Restore config selectively from the most recent `.bak` file — cherry-pick keys, don't overwrite the fresh install's `config.yaml` blindly.

**Config backups:** `C:\Users\Rob\AppData\Local\hermes\config.yaml*` shows a family of timestamped `.bak` files plus `.corrupt.` variants. The most recent `.bak-YYYYMMDD-HHMMSS` before any `.corrupt.` entry is the last known-good copy.

**Installer stuck mid-progress (e.g. "Installing Node.js dependencies").** If the installer hangs and won't complete, cancel it and use the manual path above. Do not keep the stalled installer running — it can leave partial state.

## Blank-screen diagnostic
When Hermes shows a blank screen on launch, distinguish the two failure modes before digging:
- **Plain console/cmd window** (black, no title bar, just blinking cursor) → Python backend is crashing immediately. Most likely cause: missing or broken venv. Check BOTH locations:
  ```
  Test-Path "$env:LOCALAPPDATA\hermes\venv\Scripts\python.exe"
  Test-Path "$env:LOCALAPPDATA\hermes\env\Scripts\python.exe"
  ```
  The folder may be named `env` instead of `venv`. If neither exists, the backend has no Python runtime.
- **Electron window** (title bar says "Hermes", minimize/close buttons, but empty inside) → Renderer crash. Check `desktop.log` and `errors.log`.

## APPDATA + OneDrive pitfall
Cleaning up `%LOCALAPPDATA%\hermes\` while OneDrive is actively syncing that folder can leave the venv in a broken state or cause partially-deleted files. If the user reports they "messed with APPDATA" around the same time OneDrive was uninstalled or paused, suspect filesystem corruption before assuming an app bug.

## PowerShell workflow pitfalls
**Multi-line paste collapses to one line.** Pasted multi-line blocks sometimes arrive as a single line in this user's PowerShell, breaking command parsing. Symptoms: `tart-Sleep` (the `S` got eaten) or `Start-Sleep : A positional parameter cannot be found that accepts argument 'Write-Host'`. **Default to semicolons** for sequential commands in copy-paste blocks:

```
Start-Sleep -Seconds 60; Write-Host "Re-checking..."; Get-Content "$env:LOCALAPPDATA\hermes\logs\errors.log" -Tail 50 | Select-String -Pattern "Telegram"
```

The `diagnose-hermes-desktop.ps1` script in `scripts/` does the same thing as a copy-pasteable one-liner.

**Keep ALL prose out of the fence (bit us 2026-07-27).** The user pastes blocks verbatim — an explanatory parenthetical inside the code fence gets executed as PowerShell and wedges the session at a `>>` continuation prompt. Code only inside fences; explanation outside. Recovery if it happens: `Ctrl+C`, then re-paste the code-only version.

## Update decision guidance
When Desktop shows "A new update is ready (N changes included)":
- Default to **yes, apply the update** for daily-driver use
- If user is mid-project on a working session, suggest checking the "Release notes" link first
- Don't fret over high change counts — Electron apps batch updates, "257 changes" can be a long backlog of small things
- The Danger Zone in the updater is well-labeled: "Uninstall Chat GUI only" / "Uninstall GUI + agent, keep my data" / "Uninstall everything" — granular, rollback-friendly

## Cache directory the updater sometimes chokes on
Updater error `Unable to move the cache: Access is denied`:
```
$cachePath = Get-ChildItem "$env:LOCALAPPDATA\hermes\hermes-agent\apps\desktop\release\*-unpacked\Cache" | Select-Object -First 1 -ExpandProperty FullName
Test-Path $cachePath
```
(Note the `*-unpacked` wildcard — on ARM64 devices the arch folder is `win-arm64-unpacked`, and a literal `win-unpacked` path won't exist.) If present, close Hermes fully, then `Remove-Item -Path $cachePath -Recurse -Force`. Often resolves on the first retry without needing to clear.

## Remote gateway — pointing Desktop at the VPS backend

Desktop settings → **Gateway Connection** → two cards: *Local gateway* (default, bundled backend on localhost) vs *Remote gateway* (this shell controls an already-running backend elsewhere). For the VPS-as-source-of-truth architecture, remote is the correct choice for every Windows device.

**Working values (verified on Connie):**
- Remote URL: `https://2.25.172.164` — bare, no path. nginx on 443 proxies to the dashboard backend (`hermes dashboard` on 127.0.0.1:9119). nginx vhost config: `/etc/nginx/sites-enabled/robblake.cloud`.
- Auth: a **dashboard session token** (REST + WebSocket). The token field stays hidden/disabled until the app's probe gets a response from the URL.

**Root CA cert is the prerequisite.** The VPS serves a cert signed by the Hermes-generated root (`hermes-vps-root.crt`). A device without that root in `Cert:\LocalMachine\Root` gets `NET::ERR_CERT_AUTHORITY_INVALID` in browsers AND silent probe failure in the Desktop app. Connie has it at `C:\ProgramData\Hermes\hermes-vps-root.crt`. To provision a new device: copy the .crt to the device (the Syncthing vault works as transport), then `Import-Certificate -FilePath <crt> -CertStoreLocation Cert:\LocalMachine\Root` from ADMIN PowerShell, then delete the vault copy. Browser must load `https://2.25.172.164` with NO cert warning before the Desktop app will work.

**Version gate — the Remote Gateway feature is version-sensitive.** Surface on Desktop **0.16.0** failed the probe every time (could not reach gateway yet) despite clean cert + reachable API; Connie on **0.18.2** works. The in-app updater (Settings → Updates → Update now) brought the Surface current and exposed a THIRD gateway option card, **Hermes Cloud** (Nous-hosted backend — NOT appropriate for the VPS-SSOT architecture; choose Remote gateway). After the update the remote setup could proceed. **First diagnostic question for any remote-gateway failure: compare app versions across devices** (Settings → About), before devtools or config surgery.

**Pitfall — the updater cannot complete in remote-gateway mode (hit on Connie 2026-07-19).** "Backend update failed" on a remote-mode client whose remote backend is perfectly healthy = the updater's post-update health check looks for a LOCAL backend to restart and verify; remote-mode apps don't boot one, so the check fails by construction. Fix path: flip to **Local gateway** → restart app → run the update → flip back to Remote. VERIFIED end-to-end on Connie 2026-07-27 (0.18.2 → latest, 3147-change batch): flip-local → update → flip-back worked first try, no errors. Also confirm no stale `gateway_state.json` after flipping back to Remote — on that same date the flip-back LEFT a leaked local gateway behind: `gateway_state.json` with a live PID whose CommandLine was `python -m hermes_cli.main gateway run --replace` (uv cpython 3.11), plus a second python child. Symptom: UI says Remote, status polls work, but chat messages never reach the VPS. Server-side diagnostic signature (definitive, ~1 min): nginx access.log shows the client IP doing only GET `/api/status` + `/api/profiles/sessions` polls with ZERO message POSTs, and gui.log shows no new `ws accepted` — connection healthy, message path dead = client-side leak. Fix: fully quit Hermes, `Stop-Process -Id <pid> -Force` + sweep recent python/pythonw, delete the state file, relaunch, re-verify Remote mode + no state file + no python procs. Note gui.log's ws lines all show `peer=127.0.0.1` (nginx proxy) — remote-client activity must be read from nginx access.log, not gui.log peers. Distinct from the telegram-conflict cause of the same dialog: if the device ever runs local-mode, platform conflicts can also fail the health check; in remote mode with clean logs everywhere, the mode itself is the cause.

**Pitfall — app probe fails where the browser succeeds (RESOLVED 2026-07-19: was the version gap, not cert/cache/renderer).** On the Surface at 0.16.0: browser loaded the Web UI fine after cert install, but Desktop kept showing could not reach gateway yet — check the URL / auth method will appear once it responds. Server side verified answering probes (`/api/health` → 401, `/api/status` → 200 — 401s mean reachable-but-auth-gated, which is correct). Re-selecting local→remote and re-typing the URL did not clear it. The renderer-console step (Ctrl+Shift+I → Console → Test remote) is the right diagnostic for renderer-side errors, BUT here the answer was simpler: **app version**. Desktop.log is a dead end for remote-mode probe failures — every line is still local backend boots because the probe runs in the Electron renderer. If versions match and the probe still fails with a clean browser test, THEN go to the renderer console.

**Pitfall — never hand-edit `%LOCALAPPDATA%\hermes\config.yaml` to force remote-gateway mode (attempted on Connie 2026-07-20).** That file is the CLI/local-backend config namespace (it also holds `backend: firecrawl`, `search_backend`, etc.). `backend: remote` + `gateway_url:` keys written there are NOT how the Desktop app selects its gateway mode — Desktop remote mode lives in the app's own settings store, driven by **Settings → Gateway Connection → Remote gateway** (URL + dashboard session token). Hand-editing the yaml at best does nothing and at worst wedges the local backend on next boot with a half-valid config. If it already happened: restore from the timestamped `.bak-*` copy, then use the UI path. If the UI path itself is failing (probe won't go green, token field never appears), that is a version-or-cert bug — diagnose THAT, don't work around it with config surgery.

**Token hygiene:** the session token is a full-access credential (REST + WS to the agent, incl. terminal). Same rule as every secret here — never paste it in chat. Transport it via the Syncthing vault drop-then-delete pattern, or extract server-side from the dashboard auth store.

## What this skill does NOT cover
- VPS-side gateway issues → load `operating-hermes-gateway`
- Ollama / local LLM setup → not captured
- General Hermes configuration, skills, profiles → see `hermes-agent` skill

## See also
- `references/common-errors.md` — error → cause lookup table with worked example
- `scripts/diagnose-hermes-desktop.ps1` — one-shot diagnostic PowerShell script
- `operating-hermes-gateway` — adjacent skill. Gateway is the bot dispatcher (often VPS); Desktop is the Electron client. Load it when the symptom involves platform token collisions, cron double-fire, multi-process gateway teardown, or anything that says "the bot" / "the gateway" / "the platform" rather than "the Desktop app." That skill is also the source of truth for cross-shell pitfall rules (Windows `Get-Process` vs `Get-NetTCPConnection`, multi-line PowerShell line-mangling, the copy-paste-block header rule) — they're cross-cutting, not Desktop-specific.
