{"record":{"id":"92c4ce88b04ec4ab","repo":"NousResearch/hermes-agent","slug":"delete-is-not-available","errorCode":null,"errorMessage":"Delete is not available","messagePattern":"Delete is not available","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/lib/desktop-fs.ts","lineNumber":153,"sourceCode":"// Rename a file/folder in place; returns the new absolute path. Local only.\nexport async function renameDesktopPath(path: string, newName: string): Promise<string> {\n  const desktop = bridge()\n\n  if (!desktop.renamePath) {\n    throw new Error('Rename is not available')\n  }\n\n  const result = await desktop.renamePath(path, newName)\n\n  return result.path\n}\n\n// Move a file/folder to the OS trash (recoverable). Local only.\nexport async function trashDesktopPath(path: string): Promise<void> {\n  const desktop = bridge()\n\n  if (!desktop.trashPath) {\n    throw new Error('Delete is not available')\n  }\n\n  await desktop.trashPath(path)\n}\n\nexport async function copyTextToClipboard(text: string): Promise<void> {\n  await bridge().writeClipboard(text)\n}\n\n// Working-tree-vs-HEAD diff for one file. Empty when unchanged / not a repo.\n// Remote gateway → backend git (/api/git/file-diff); local → Electron git.\nexport async function desktopFileDiff(repoRoot: string, filePath: string): Promise<string> {\n  if (isDesktopFsRemoteMode()) {\n    const result = await remoteFsApi<{ diff: string }>(\n      `/api/git/file-diff?path=${encodeURIComponent(repoRoot)}&file=${encodeURIComponent(filePath)}`\n    )\n\n    return result.diff || ''","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/src/lib/desktop-fs.ts#L135-L171","documentation":"Thrown by trashDesktopPath() in apps/desktop/src/lib/desktop-fs.ts:153 when the bridge lacks a `trashPath` method. Moving a file to the OS trash (recoverable delete) is a local-only Electron IPC capability; without it the function refuses, which is the safe behavior — a fallback that permanently deleted would be worse than the error.","triggerScenarios":"Calling trashDesktopPath on an older shell without the trashPath IPC; a remote-mode session where local-only capabilities are not offered; a bridge stub in tests missing trashPath.","commonSituations":"Version skew between renderer and Electron shell; delete button shown in a context where the capability genuinely doesn't exist; web preview importing the file explorer UI.","solutions":["Gate the delete/trash UI on the capability: `if (!window.hermesDesktop?.trashPath) disableDelete()`.","Update the desktop shell and restart so trashPath is exposed.","On remote sessions, delete through the backend FS API instead of the local trash.","Stub trashPath in bridge mocks for tests."],"exampleFix":"// before\nawait trashDesktopPath(path)\n\n// after\nif (!window.hermesDesktop?.trashPath) {\n  notify({ kind: 'error', title: 'Delete requires an updated desktop app' })\n  return\n}\nawait trashDesktopPath(path)","handlingStrategy":"type-guard","validationCode":"function canTrash(): boolean {\n  return typeof window.hermesDesktop?.trashPath === 'function'\n}","typeGuard":"function hasTrash(w: Window): w is Window & { hermesDesktop: { trashPath: (p: string) => Promise<void> } } {\n  return typeof (w as any).hermesDesktop?.trashPath === 'function'\n}","tryCatchPattern":"if (!canTrash()) { disableDeleteAction(); return }\ntry { await trashDesktopPath(path) } catch (e) { notifyError(e, 'Delete failed') }","preventionTips":["Never substitute an unconditional delete when trashPath is missing — the error is the safe outcome","Gate destructive UI on capability presence","Keep local-only capabilities hidden in remote mode","Stub trashPath in tests"],"tags":["desktop","electron","filesystem","delete","capability"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}