{"record":{"id":"c403663883319b42","repo":"moeru-ai/airi","slug":"openresult","errorCode":null,"errorMessage":"${openResult}","messagePattern":"\\$\\{openResult\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/electron/app.ts","lineNumber":18,"sourceCode":"import type { createContext } from '@moeru/eventa/adapters/electron/main'\nimport type { BrowserWindow } from 'electron'\n\nimport { defineInvokeHandler } from '@moeru/eventa'\nimport { app, shell } from 'electron'\nimport { isLinux, isMacOS, isWindows } from 'std-env'\n\nimport { electron, electronAppOpenUserDataFolder, electronAppQuit } from '../../../shared/eventa'\n\nexport function createAppService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow }) {\n  defineInvokeHandler(params.context, electron.app.isMacOS, () => isMacOS)\n  defineInvokeHandler(params.context, electron.app.isWindows, () => isWindows)\n  defineInvokeHandler(params.context, electron.app.isLinux, () => isLinux)\n  defineInvokeHandler(params.context, electronAppOpenUserDataFolder, async () => {\n    const path = app.getPath('userData')\n    const openResult = await shell.openPath(path)\n    if (openResult) {\n      throw new Error(openResult)\n    }\n    return { path }\n  })\n  defineInvokeHandler(params.context, electronAppQuit, () => app.quit())\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/apps/stage-tamagotchi/src/main/services/electron/app.ts#L1-L24","documentation":"Thrown by the electronAppOpenUserDataFolder IPC handler. Electron's shell.openPath(path) returns an empty string on success and a non-empty error message string on failure; the handler throws that message verbatim. So the error text is OS-supplied (e.g. 'Failed to open path') rather than a hardcoded constant.","triggerScenarios":"User clicks 'Open User Data Folder' in settings; the userData directory does not exist yet, has been deleted, has restrictive permissions, or no application is registered to open a folder on the OS.","commonSituations":"First run before userData was created; folder moved/deleted externally; Linux without a file manager handler; permissions locked by another process; sandboxed environment blocking shell.openPath.","solutions":["Ensure app.getPath('userData') exists before calling openPath — mkdir it if missing.","Check OS file associations for directory opening (xdg-open on Linux, Finder on macOS).","On Linux, verify xdg-utils is installed and XDG_CURRENT_DESKTOP is set.","Run the app outside a locked-down sandbox that blocks process spawning."],"exampleFix":"// before\nconst path = app.getPath('userData')\nconst openResult = await shell.openPath(path)\nif (openResult) {\n  throw new Error(openResult)\n}\n\n// after\nconst path = app.getPath('userData')\nawait fs.promises.mkdir(path, { recursive: true })\nconst openResult = await shell.openPath(path)\nif (openResult) {\n  throw new Error(`Could not open user data folder: ${openResult}`)\n}","handlingStrategy":"try-catch","validationCode":"import { existsSync, mkdirSync } from 'node:fs'\nimport { app } from 'electron'\nfunction ensureUserDataFolder(): string {\n  const path = app.getPath('userData')\n  if (!existsSync(path)) mkdirSync(path, { recursive: true })\n  return path\n}","typeGuard":null,"tryCatchPattern":"try {\n  await invokeOpenUserDataFolder()\n} catch (e) {\n  // e.message is OS-supplied; surface to user, suggest manual path\n  showError(`Could not open the folder automatically. Open it manually: ${app.getPath('userData')}`)\n}","preventionTips":["Create userData with mkdir recursive before openPath.","Provide a fallback 'copy path' button when openPath fails.","Verify OS screen-recording/file-manager associations on Linux."],"tags":["electron","filesystem","os","ipc","shell"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}