{"record":{"id":"422a78d1a4d12cb1","repo":"NousResearch/hermes-agent","slug":"invalid-preview-url","errorCode":null,"errorMessage":"Invalid preview URL","messagePattern":"Invalid preview URL","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/desktop/electron/main.ts","lineNumber":11410,"sourceCode":"  // The match count arrives asynchronously via `found-in-page`; the\n  // synchronous return value is intentionally `{ count: 0 }` to mirror\n  // Electron's own `findInPage` return semantics (an opaque request id).\n  return { count: 0 }\n})\n\nipcMain.handle('hermes:stop-find-in-page', event => {\n  const win = BrowserWindow.fromWebContents(event.sender)\n\n  if (!win || win.isDestroyed()) {\n    return\n  }\n\n  stopFind(win.webContents)\n})\n\nipcMain.handle('hermes:openPreviewInBrowser', async (_event, url) => {\n  if (!(await openPreviewInBrowser(url))) {\n    throw new Error('Invalid preview URL')\n  }\n})\n\n// User-configurable default project directory. The renderer reads this on\n// settings mount and seeds the value into the picker; writing back persists\n// it via writeDefaultProjectDir so resolveHermesCwd picks it up on the next\n// session spawn (no app restart needed).\nipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({\n  dir: readDefaultProjectDir(),\n  defaultLabel: app.getPath('home'),\n  resolvedCwd: resolveHermesCwd()\n}))\n\nipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))\n\nipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {\n  const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null\n","sourceCodeStart":11392,"sourceCodeEnd":11428,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/main.ts#L11392-L11428","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nonClick={() => void ipc.invoke('hermes:openPreviewInBrowser', previewUrl)}\n\n// after\nonClick={() => { if (!previewUrl) return; void ipc.invoke('hermes:openPreviewInBrowser', previewUrl) }}","handlingStrategy":"validation","validationCode":"if (!previewUrl) { notify('No preview URL available yet'); return }\nawait ipc.invoke('hermes:openPreviewInBrowser', previewUrl)","typeGuard":"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 } }","tryCatchPattern":"catch (e) { if (e instanceof Error && e.message === 'Invalid preview URL') notify('Preview URL rejected — is the dev server running?') else throw e }","preventionTips":["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"],"tags":["security","url-validation","ipc","desktop","preview"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}