NousResearch/hermes-agent · warning · Error
Invalid preview URL
Error message
Invalid preview URL
What it means
The 'hermes:openPreviewInBrowser' IPC validates the URL via openPreviewInBrowser(url) (an async guard, typically restricting previews to localhost/loopback dev-server origins) before opening the system browser. A URL outside the allowed preview set, a malformed string, or a non-URL makes the guard resolve false and this error throws. Like openExternal, it is a guard, not a connectivity failure.
Source
Thrown at apps/desktop/electron/main.ts:11410
// The match count arrives asynchronously via `found-in-page`; the
// synchronous return value is intentionally `{ count: 0 }` to mirror
// Electron's own `findInPage` return semantics (an opaque request id).
return { count: 0 }
})
ipcMain.handle('hermes:stop-find-in-page', event => {
const win = BrowserWindow.fromWebContents(event.sender)
if (!win || win.isDestroyed()) {
return
}
stopFind(win.webContents)
})
ipcMain.handle('hermes:openPreviewInBrowser', async (_event, url) => {
if (!(await openPreviewInBrowser(url))) {
throw new Error('Invalid preview URL')
}
})
// User-configurable default project directory. The renderer reads this on
// settings mount and seeds the value into the picker; writing back persists
// it via writeDefaultProjectDir so resolveHermesCwd picks it up on the next
// session spawn (no app restart needed).
ipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({
dir: readDefaultProjectDir(),
defaultLabel: app.getPath('home'),
resolvedCwd: resolveHermesCwd()
}))
ipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))
ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null
View on GitHub (pinned to c896c09c42)
Solutions
- Print/inspect the url argument — confirm it is an absolute http(s) URL on an allowed (usually loopback) origin
- Wait for the preview/dev-server ready event that carries the final URL before enabling the open-in-browser button
- If a non-loopback preview origin is legitimate, extend openPreviewInBrowser's allow-list in the main process rather than bypassing it
- Disable the button when no preview URL is set instead of invoking the IPC with a stale value
Example fix
// before
onClick={() => void ipc.invoke('hermes:openPreviewInBrowser', previewUrl)}
// after
onClick={() => { if (!previewUrl) return; void ipc.invoke('hermes:openPreviewInBrowser', previewUrl) }} Defensive patterns
Strategy: validation
Validate before calling
if (!previewUrl) { notify('No preview URL available yet'); return }
await ipc.invoke('hermes:openPreviewInBrowser', previewUrl) Type guard
function isLoopbackHttpUrl(v: string): boolean { try { const u = new URL(v); return ['http:', 'https:'].includes(u.protocol) && ['localhost', '127.0.0.1', '[::1]'].includes(u.hostname) } catch { return false } } Try / catch
catch (e) { if (e instanceof Error && e.message === 'Invalid preview URL') notify('Preview URL rejected — is the dev server running?') else throw e } Prevention
- Only enable open-in-browser after the preview server reports ready with a concrete URL
- Construct preview URLs as absolute http(s) URLs on loopback hosts
- Treat a rejected preview URL as a signal the dev server isn't up, not as a browser bug
When it happens
Trigger: Renderer requests opening a preview for a URL that is not an approved local preview origin (e.g. a remote https URL, a bare port '3000', undefined), or the preview server URL was constructed before the dev server reported its port.
Common situations: Clicking 'open in browser' for a preview pane whose dev server hasn't started or whose URL template produced null/undefined; passing a LAN address where the guard only allows 127.0.0.1/localhost.
Related errors
- Invalid external URL
- gh pr create failed (is gh installed and authenticated?)
- Branch name is required.
- Missing URL
- Could not create directory: ${error.message}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/422a78d1a4d12cb1.
Report an issue: GitHub.